学完排序算法,终于知道用什么方法给监考完收上来的试卷排序……

由于每个老师批改完卷子之后装袋不一定是有序的,鼠鼠我被拉去当给试卷排序的苦力。面对堆积成山的试卷袋,每一份试卷袋的试卷集又很重,鼠鼠我啊为了尽早下班,决定用一种良好的办法进行排序。

1.插入排序

首先考虑的是插入排序。第一份试卷就当做已经有序了;第二份试卷序号如果比第一份试卷小,那么放在第一份试卷的上面,否则放在下面;第三份试卷又放在前两份试卷集合的正确位置。总之,保证我拿到一份试卷,这一份试卷就插入到了正确的位置。

这一个排序算法看起来是生活中用的最常见的(因为看上去其他苦力鼠鼠也是这么干的),代码也是比较好写的:i迭代完1次说明1个元素有了相对排好序的位置。注意,第一次我们跳过了i=0,实际上默认第一个就是相对有序的(本来它就只有一个,所以有序)。i = 1,说明我们正在给i=1的试卷进行插入操作,而前面如果排好序的元素大于它,就一个一个往后移,直到找到一个学号小于当前我们拿到的试卷上填的学号,我们就放在这个试卷的后面。

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        for(int i = 1; i < nums.size(); ++i){//从第二个开始排
            int key = nums[i];
            int j = i - 1;
            while(j >= 0 && nums[j] > key){
                nums[j + 1] = nums[j];
                j = j - 1;
            }
            nums[j + 1] = key;

        }
        return nums;
    }
};

再来一个注释版本,跟上面是一样的

#include <vector>
using namespace std;

class Solution {
public:
    void insertionSort(vector<int>& nums) {
        int n = nums.size();
        for (int i = 1; i < n; i++) {
            int key = nums[i]; // 取出未排序部分的第一个元素
            int j = i - 1;

            // 将大于key的元素向后移动一个位置
            while (j >= 0 && nums[j] > key) {
                nums[j + 1] = nums[j];
                j = j - 1;
            }
            nums[j + 1] = key; // 找到了key的正确位置,插入key
        }
    }

    vector<int> sortArray(vector<int>& nums) {
        insertionSort(nums);
        return nums;
    }
};

2. 改良:归并+插入排序

但是试卷实在太重了,一个班有60个人,每个人有2页试卷,2页答题纸,2页草稿纸……排序到后面,鼠鼠真的没有力气捧着排好序的试卷挨个插入。

于是聪明的鼠鼠想了一个办法:把试卷分成两叠(或者更多叠,看试卷一共有多少份)。两叠内用插入排序,那么能够得到两叠排好序的试卷,这样鼠鼠每次捧在手上的试卷少了,插入排序也排的很轻松(知识点:插入排序对小数据集非常高效)。

而针对两叠有序的试卷,就可以进行归并排序了。注意,这只是归并排序中两块merge的部分操作,而省去了归并排序中递归拆分到大小为1然后再两两merge的操作。简单来说,这里使用到了归并排序的最后一步,在我参考的博客中找了个图如下,用到的是红框中标出的排序理念:
在这里插入图片描述
此时,我们其实是双指针分别指着两堆试卷的头,哪个序号小就放在temp数组里面,最后我们的temp数组就存放了所有的排好序的试卷(真是伟大的鼠鼠,聪明的鼠鼠!)如果一个班的同学更多一点,那么我们实际上可以将其分成四份,合并过程则为该图的后三行。1、2两个数组先合并,3、4两个数组先合并,最后剩下来的两个数组继续合并。

归并排序算法如下:

代码思路也很简单,主要是用到的递归。第一个merge块其实就是做了我刚刚给两坨有序试卷双指针飞快排序的过程;而第二个merge_recursive实际上是做了图上第一块讲的“一拆二”的过程,拆到每份试卷只剩一个,再进行merge。

class Solution {
public:
    void merge(int left,  int mid, int right,vector<int>& nums, vector<int>& temp){
        int i = left;
        int j = mid + 1;
        int k = 0;
        while(i <= mid && j <= right){
            if(nums[i] <= nums[j])
                temp[k++] = nums[i++];
            else temp[k++] = nums[j++];
        }
        while(i <= mid){
            temp[k++] = nums[i++];
        }
        while(j <= right){
            temp[k++] = nums[j++];
        }

        for (i = left, k = 0; i <= right; i++, k++) {
            nums[i] = temp[k];
        }
    }

    void merge_recursive(int left, int right, vector<int>&nums, vector<int>& temp){
        if(left < right){
            int mid = left + (right - left)/2;
            merge_recursive(left, mid, nums, temp);
            merge_recursive(mid + 1, right, nums, temp);
            merge(left, mid, right, nums, temp);
        }
    }
    vector<int> sortArray(vector<int>& nums) {
        vector<int> temp(nums.size());
        merge_recursive(0, nums.size() - 1, nums, temp);
        return nums;
    }
};

下面仍旧是一个写好注释的版本:

#include <vector>
using namespace std;

// 归并操作,合并两个有序数组段[arr[left..mid]]和[arr[mid+1..right]]为一个有序数组段[arr[left..right]]
// temp是一个临时数组,用于存储合并后的有序序列,避免直接在原数组上操作导致的数据错乱
void merge(vector<int>& arr, int left, int mid, int right, vector<int>& temp) {
    int i = left; // 左半部分的起始位置
    int j = mid + 1; // 右半部分的起始位置
    int k = 0; // temp数组的当前索引

    // 遍历两个数组,按顺序选择较小的元素放入temp中
    while (i <= mid && j <= right) {
        if (arr[i] <= arr[j]) {
            temp[k++] = arr[i++];
        } else {
            temp[k++] = arr[j++];
        }
    }

    // 如果左半部分还有剩余,将剩余元素复制到temp中
    while (i <= mid) {
        temp[k++] = arr[i++];
    }

    // 如果右半部分还有剩余,将剩余元素复制到temp中
    while (j <= right) {
        temp[k++] = arr[j++];
    }

    // 将排序后的temp数组复制回原数组arr
    for (i = left, k = 0; i <= right; i++, k++) {
        arr[i] = temp[k];
    }
}

// 递归执行归并排序
// left是数组段的起始位置,right是数组段的结束位置
void mergeSortRecursive(vector<int>& arr, int left, int right, vector<int>& temp) {
    if (left < right) { // 如果当前数组段不是单元素
        int mid = left + (right - left) / 2; // 计算中间位置
        // 递归排序左半部分
        mergeSortRecursive(arr, left, mid, temp);
        // 递归排序右半部分
        mergeSortRecursive(arr, mid + 1, right, temp);
        // 合并两个有序的部分
        merge(arr, left, mid, right, temp);
    }
}

// 归并排序的主函数,返回排序后的数组
vector<int> sortArray(vector<int>& nums) {
    vector<int> temp(nums.size()); // 创建一个临时数组,大小与原数组相同
    mergeSortRecursive(nums, 0, nums.size() - 1, temp); // 从整个数组的范围开始递归排序
    return nums;
}

3.在排好试卷以后……

鼠鼠希望找到更加高效的方法,于是深入学习了冒泡排序、快速排序、堆排序、选择排序、希尔排序、桶排序和基数排序。在这里与大家分享学习心得,让大家加入鼠鼠大家庭 更快地排序。

对于桶排序、基数排序、希尔排序,不打算写代码,所以浅讲思路。

3.1 桶排序:

假设我们有一组0到99的数字,我们用10个桶来对它们进行排序,每个桶负责一个范围:0-9, 10-19, …, 90-99。将数字根据它们的十位数分配到对应的桶中。
每个桶里面,再进行排序,这里随便你选用什么算法,最好还是插入排序,又稳定,又适应小数据集。这样,每个桶内都是有序的。如果选择插入排序,那么顺带着整个桶排序都是稳定的。
此时再对桶间进行排序。由于每个桶对应的数字范围是不重叠的,我们只需要按桶的顺序依次将桶内的元素合并起来,就可以得到一个有序数组,所以桶间不需要排序。

3.2 基数排序

假设有很多长度相同的数字,我们有0-9 一共10个桶。个位为1的全放到桶1,为2的全放到桶2,……按个位排好了再去排十位,百位。排到最后一位,就是全部有序的了。

3.3 希尔排序

希尔排序是插入排序的升级版,它的优势在于能够快速地把较大的数放到该去的位置附近。第一次排序以较大的一个数x作为间隔,相隔x的数都收集起来进行一个插入排序;然后间隔减小,再插入排序;最后间隔减小到1,此时就已经基本上完全有序了。这个解释不清楚,需要看动图理解。此处略过。

4. 冒泡排序、快速排序、选择排序、堆排序

4.1 冒泡排序

使用flag来标明排好序,也就是说在一次遍历的过程中没有发生任何交换。注意,看代码是从j开始看,外层的i仅仅用来表明j应该到哪里结束(每一次循环,把最大的放到nums的尾部,这样尾部就不用参加两两交换的过程了。)

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        int n = nums.size();
        bool flag = true;
        for(int i = 0; flag && i < n - 1; ++i){
            bool flag = false;
            for(int j = 0; j < n - i - 1; ++j){
                if(nums[j] > nums[j + 1]){
                    flag = true;
                    // swap(nums[j], nums[j + 1]);
                    nums[j] = nums[j]^nums[j + 1];
                    nums[j + 1] = nums[j] ^ nums[j + 1];
                    nums[j] = nums[j] ^ nums[j + 1];
                }
            }
        }
        return nums;
    }
};

4.2 快速排序

这里我使用了双指针,似乎还有什么单指针,还有随机化、三向之类的用于描述快排算法细分类的写法。我自定义这里为未随机化(因为每次基准选的是最后一位)+二向(我是两个方向,前后夹击),不知道是否正确,总之感觉这是一个比较好理解的代码。
在quickSort函数里,我们定义了partition_id,并且根据这个partition_id左右切分,继续快排,直到start >= end, 也就是说没有计算partition_id的必要了。
再看partition函数,左右指针分别抓取一个不合规的元素(左边指针指着比最后一个元素小的值,右边指针指着一个比最后一个元素大的值),然后交换,一直到左右指针重叠,或者左指针大于右指针。此时把最后一个元素和左指针指向的元素进行交换(这里如果不懂为什么是左指针指向的,手动排个序试一试吧),返回的partition就是left指针指向的id。

class Solution {
public:
    int partition(int start, int end, vector<int>& nums){
        int pivot = end;
        int left = start;
        int right = end - 1;

        while(1){
            while(left <= right && nums[left] <= nums[pivot]){
                left++;
            }

            while(left <= right && nums[right] > nums[pivot]){
                right--;
            }
            if(left >= right) break;
            swap(nums[left], nums[right]);
            left++;
            right--;
        }

        swap(nums[pivot], nums[left]);
        return left;
    }

    void quickSort(int start, int end, vector<int>& nums){
        if(start < end){
            int partition_id = partition(start, end, nums);
            quickSort(start, partition_id - 1, nums);
            quickSort(partition_id + 1, end, nums);
        }
    }

    vector<int> sortArray(vector<int>& nums) {
        quickSort(0, nums.size() - 1, nums);
        return nums;
    }
};

4.3 选择排序

我宣布选择排序是最简单的排序算法,就是选最小值,放到第一个,选后面最小值,放到第二个。

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        for(int i = 0; i < nums.size(); ++i){
            int min_index = i;
            for(int j = i; j < nums.size(); ++j){
                if(nums[j] < nums[min_index]) min_index = j;
            }
            swap(nums[i], nums[min_index]);
        }

        return nums;
    }
};

4.4 堆排序

堆排序让我很意外时间复杂度是O(nlogn),其实是不信建堆时间只要O(n),但是我们对n/2 个节点以它们为根节点的树进行调整,确实是每个只要跟子节点进行比较一下就好了,如果有涉及到子节点调整那也不会增加太多时间(没细看这里),所以说确实是O(n)的建堆时间。heapSort里先对于非叶子节点一层一层往上调整根为最大,然后如果交换完了则需要对交换的子节点再次调整。heapify即是一个能够把当前i节点视为树根节点,并且保证i节点为树里最大节点、底下节点也满足根最大的一个函数

class Solution {
public:
    void heapify(vector<int>&nums, int n, int i){
        int largest = i;
        int left = 2*i + 1;
        int right = 2*i + 2;
        if(left < n && nums[left] > nums[largest])
            largest = left;
        if(right < n && nums[right] > nums[largest])
            largest = right;
        if(largest != i){
            swap(nums[i], nums[largest]);
            heapify(nums, n, largest);
        }
    }
    
    void heapSort(vector<int>& nums){
        int n = nums.size();
        // 构建最大堆
        for(int i = n/2 - 1; i >= 0; i--){
            heapify(nums, n, i);//nums一共是n个节点,根是第i个节点
        } 
        //堆顶取值
        for(int i = n - 1; i > 0; i--){
            swap(nums[0],nums[i]);
            heapify(nums, i, 0);//剩下要调整的nums一共是i个节点,根是第0个节点
        }
    }
    
    vector<int> sortArray(vector<int>& nums) {
        heapSort(nums);
        return nums;
    }
};

至于稳定性、时间复杂度,鼠鼠又搬运一波
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.kler.cn/a/273068.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

VS2022 配置QT5.9.9

QT安装 下载地址&#xff1a;https://download.qt.io/archive/qt/ 下载安装后进行配置 无法运行 rc.exe 下载VS2022 官网下载 配置 1.扩展-管理扩展-下载Qt Visual Studio Tools 安装 2.安装完成后&#xff0c;打开vs2022,点击扩展&#xff0c;会发现多出了QT VS Tools,点…

hcia复习总结9

NAT 在ip地址空间中&#xff0c;A,B,C三类地址中各有一部分地址&#xff0c;他们被称为私有地址&#xff08;私网IP地址&#xff09;&#xff0c;其余的所有地址都被称为公有地址&#xff08;公网IP地址&#xff09; A&#xff1a;10.0.0.0-10.255.255.255--相当于一个A类网络…

Milvus向量数据库检索

官方文档&#xff1a;https://milvus.io/docs/search.md   本节介绍如何使用 Milvus 搜索实体。   Milvus 中的向量相似度搜索会计算查询向量与具有指定相似度度量的集合中的向量之间的距离&#xff0c;并返回最相似的结果。您可以通过指定过滤标量字段或主键字段的布尔表达…

【大数据面试题】 018 数据仓库的分层了解吗?说说你的理解

一步一个脚印&#xff0c;一天一道面试题。 数据仓库是比较常见的考点。今天就介绍一下数据仓库的分层。本篇文章会较多的图片是来自尚硅谷的。 数据仓库的背景和好处 数据仓库的诞生就和大数据的诞生有很大的相似。大数据的诞生是为了处理超大的数据&#xff0c;并在其中探…

最新WordPress网址导航设计师主题风格网站源码

内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 最新WordPress精品网址导航主题整站源码WAP端修复tab标签ajax加载模式会显示未审核的网址的bug小屏幕热搜采用水平滚动优化子主题支持添加文章分页 二、效果展示 1.部分代码 代码如…

基于vue实现bilibili网页

学校要求的实验设计,基于vue实现bilibili网页版,可实现以下功能 (1)基本的悬浮动画和页面渲染 (2)可实现登录和未登录的页面变化 (3)在登录页面的,实现密码判断,或者短信验证方式的倒数功能 (4)实现轮播图 (5)实现预览视频(GIF) (6)页面下拉到一定高度出现top栏以及右下角的返回…

springboot277流浪动物管理系统

流浪动物管理系统设计与实现 摘 要 在如今社会上&#xff0c;关于信息上面的处理&#xff0c;没有任何一个企业或者个人会忽视&#xff0c;如何让信息急速传递&#xff0c;并且归档储存查询&#xff0c;采用之前的纸张记录模式已经不符合当前使用要求了。所以&#xff0c;对流…

HTML_CSS练习:HTML注释

一、代码示例 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>HTML注释</title> </head> <body><marquee loop"1">马龙强<!--下面的输入框是可以滚动的&#x…

掘根宝典之C++RTTI和类型转换运算符

什么是RTTI RTTI是运行阶段类型识别的简称。 哪些是RTTI? C有3个支持RTTI的元素。 1.dynamic_cast运算符将使用一个指向基类的指针来生成一个指向派生类的指针&#xff0c;否则该运算符返回0——空指针。 2.typeid运算符返回一个指出对象类型的信息 3.type_info结构存储…

提升地理空间分析效率,火山引擎ByteHouse上线GIS能力

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 在数字化时代&#xff0c;地理空间分析&#xff08;Geospatial Analytics&#xff09;成为辅助企业市场策略洞察的重要手段。无论是广告投放的精准定位&#xff0c;…

基于正点原子潘多拉STM32L496开发板的简易示波器

一、前言 由于需要对ADC采样性能的评估&#xff0c;重点在于对原波形的拟合性能。 考虑到数据的直观性&#xff0c;本来计划采集后使用串口导出&#xff0c;并用图形做数据拟合&#xff0c;但是这样做的效率低下&#xff0c;不符合实时观察的需要&#xff0c;于是将开发板的屏幕…

【Unity】Transform、Rigidbody、CharacterController移动

前言 在使用Unity开发的时候&#xff0c;移动是最最基础的一个需求&#xff0c;我来给大家简单的讲一下Unity中的几种常见的移动方法。 1.Transform移动 Transform移动就是修改物体的position ①修改位置 这里要注意&#xff1a;坐标分为世界坐标和本地坐标 //将物体的世界坐…

Linux:搭建ntp服务器

我准备两个centos7服务器 一个为主服务器连接着外网&#xff0c;并且搭建了ntp服务给其他主机同步 另外一个没有连接外网&#xff0c;通过第一台设备去同步时间 首先两个服务器都要安装ntp软件 yum -y install ntp 再把他俩的时间都改成别的 左侧的是主服务器&#xff0c;主…

Python面试笔记

Python面试笔记 PythonQ. Python中可变数据类型与不可变数据类型&#xff0c;浅拷贝与深拷贝详解Q. 解释什么是lambda函数&#xff1f;它有什么好处&#xff1f;Q. 什么是装饰器&#xff1f;Q. 什么是Python的垃圾回收机制&#xff1f;Q. Python内置函数dir的用法&#xff1f;Q…

Vue.js+SpringBoot开发食品生产管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 加工厂管理模块2.2 客户管理模块2.3 食品管理模块2.4 生产销售订单管理模块2.5 系统管理模块2.6 其他管理模块 三、系统展示四、核心代码4.1 查询食品4.2 查询加工厂4.3 新增生产订单4.4 新增销售订单4.5 查询客户 五、…

【GPT-SOVITS-02】GPT模块解析

说明&#xff1a;该系列文章从本人知乎账号迁入&#xff0c;主要原因是知乎图片附件过于模糊。 知乎专栏地址&#xff1a; 语音生成专栏 系列文章地址&#xff1a; 【GPT-SOVITS-01】源码梳理 【GPT-SOVITS-02】GPT模块解析 【GPT-SOVITS-03】SOVITS 模块-生成模型解析 【G…

18个惊艳的可视化大屏(第26辑):航空与运输业

hello&#xff0c;我是贝格前端工场老司机&#xff0c;这是第26期了&#xff0c;本次带来可视化大屏在航空与运输业的应用案例&#xff0c;喜欢文章的别忘点赞关注&#xff0c;文章底部也有其他行业的案例。 可视化大屏在航空与运输业中具有以下九大价值&#xff1a; 实时监控…

基于单片机的老人防丢系统设计

目 录 摘 要 I Abstract II 引 言 3 1 系统总体架构 6 1.1方案设计与选择 6 1.2 系统架构设计 6 1.3 系统器件选择 7 2 系统硬件设计 9 2.1 单片机外围电路设计 9 2.2 LCD1602液晶显示电路设计 12 2.3 短信模块电路设计 14 2.4 GPS模块电路设计 14 2.5 电源与按键控制电路设计…

python 基础知识点(蓝桥杯python科目个人复习计划65)

今日复习内容&#xff1a;做题 例题1&#xff1a;遥远的雪国列车 问题描述&#xff1a; 小蓝和小红今天在房间里一起看完了“雪国列车”这部电影&#xff0c;看完之后他们感触颇深&#xff0c;同时他们想到了这样一道题目&#xff1a; 现在有一个数轴&#xff0c;长度为N&a…

yocto系列之针对从git仓库获取源代码编写recipe

回顾 针对借助yocto构建linux 镜像我们已经讲述了7部分&#xff0c; 简单回顾如下&#xff1a; Yocto: 第1部分 - yocto系列之yocto是个什么东东 https://mp.csdn.net/mp_blog/creation/editor/136742286 Yocto: 第2部分 - yocto系列之配置ubuntu主机 https://mp.csdn.net…
最新文章