数据结构之排序

article/2025/6/25 0:55:24

正值数据结构期末复习之际,遂将排序相关的复习内容记录在此,也算是对知识的一种整理归纳和复习。

常见的排序方法:插入排序、交换排序、选择排序、归并排序、基数排序

其中插入排序又包含直接插入排序、折半插入排序、希尔排序。

交换排序有包括冒泡排序、快速排序。

选择排序又包括简单选择排序、树形选择排序、堆排序。

我们本篇介绍常见的排序方法,默认按升序排列实现。

目录

一、插入排序

1、直接插入排序

(1)、排序思想

(2)、代码实现

(3)、复杂度分析

(4)、算法特点

2、折半插入排序

(1)、排序思想

(2)、代码实现

(3)、复杂度分析

(4)、算法特点

3、希尔排序

(1)、排序思想

(2)、代码实现

(3)、复杂度分析

(4)、算法特点

二、交换排序

1、冒泡排序

(1)、排序思想

(2)、代码实现

(3)、复杂度分析

(4)、算法特点

2、快速排序

(1)、排序思想

(2)、代码实现

(3)、复杂度分析

(4)、算法特点

三、选择排序

1、直接选择排序

(1)、排序思想

(2)、代码实现

(3)、复杂度分析

(4)、算法特点

2、堆排序

(1)、排序思想

(2)、代码实现

(3)、复杂度分析

(4)、算法特点

四、归并排序

1、归并排序

(1)、排序思想

(2)、代码实现

(3)、复杂度分析

(4)、算法特点

五、基数排序

六、总结


一、插入排序

1、直接插入排序

(1)、排序思想

        首先,对于一个数,其肯定是有序的。那么我们的排序思想是将一个待排序的关键字插入已经排序好的表中,从而得到一个新的,关键字数量加1的有序表。将第二个关键字插入只有1个数的有序表中,将第三个关键字有两个数的有序表中,以此类推。每次将待排序(即有序表最后一个的下一个位置)的和前面的有序表的最后一个比较,如果比其小,则将该位置的元素往后移,空出该位置来,如果大于等于,那么就在该位置的下一个位置插入。

(2)、代码实现

void InsertSort(int* arr, int n) //默认按升序实现
{for (int i = 0; i < n - 1; i++){int end = i;int tmp = arr[end + 1]; //arr[end+1]是待排序的关键字while (end >= 0){if (arr[end] > tmp){arr[end + 1] = arr[end];end--;}else{break;}}arr[end + 1] = tmp;}
}

(3)、复杂度分析

时间复杂度:需要比较n-1趟,最好情况时,总的比较次数达最小值n-1.最坏情况下,总的比较次数和移动次数均达到最大值,都大约为n方/2。因此总体时间复杂度为O(N*2)

空间复杂的:显而易见为O(1),几乎没有借助额外的辅助空间。‘

(4)、算法特点

稳定排序,适用于顺序结构和链式结构,当初始状态越有序时算法效率越好。

2、折半插入排序

(1)、排序思想

基本思想与上述直接插入排序相同,均为将一个待排序的元素插入有序表中,不同的是在进行比较的时候不是从前往后比,而是采用二分思想比较,不断缩小区间,平均情况下可以减少关键字的比较次数,但移动次数不变。

(2)、代码实现

void BinaryInsertSort(int* arr, int n)
{for (int i = 0; i < n - 1; i++){int tmp = arr[i + 1];int left = 0, right = i;while (left <= right){int m = (left + right) / 2;if (tmp < arr[m]){right = m - 1;}else{left = m + 1; //无论插入位置在数组中间还是末尾,left 始终指向第一个大于 tmp 的元素位置}}// 将插入位置之后的元素后移for (int j = i; j >= left; j--){arr[j + 1] = arr[j];}arr[left] = tmp;}
}

(3)、复杂度分析

时间复杂度:在平均情况下,折半插入排序仅减少了关键字的比较次数,而记录的移动次数不变,因此,折半插入排序的时间复杂度仍为O(N*2)]。

空间复杂度:几乎没有使用额外的辅助空间,空间复杂度为O(N)。

(4)、算法特点

稳定排序、只能用于顺序结构。

3、希尔排序

(1)、排序思想

希尔排序又称缩小增量排序。其基本思想是分组。先选定一个整数gap,把待排序的所有元素分成各组,所有距离相等的在一组内,并对每一组内的记录进行排序,然后gap=gap/3+1得到下一个整数,(这里gap的迭代方式自己定,只需要确保最后一次是1即分为1组即可。不过通常认为/3+1或/2+1比较好),再继续分组,进行插入排序,当gap=1时,就相当与直接插入排序。

我们用接下来这个图演示。

初始时gap=5,将距离每隔5的元素分成一组,然后对每组进行插入排序,然后就得到了稍微那么有点序的第二趟数组,此时gap迭代更新为2,距离每隔2的元素分组,然后继续插入排序得到了更有序一点的第三趟数组,然后此时gap迭代为1,对整个数组进行直接插入排序。注意对于直接插入排序越接近有序排序效率越高。

(2)、代码实现

void ShellSort(int* arr, int n)
{int gap = n;while (gap > 1){gap = gap / 3 + 1;for (int i = 0; i < n - gap; i++){int end = i;int tmp = arr[end + gap]; //取同一组内有序表最后一个元素的下一个位置的元素while (end >= 0){if (arr[end] > tmp){arr[end + gap] = arr[end];end -= gap;}else{break;}}arr[end + gap] = tmp;}}
}

(3)、复杂度分析

时间复杂度:希尔排序的时间复杂度是一个难题,对于增量序列的不同,起时间复杂度也不同,当n在某个特定范围内,希尔排序所需的比较和移动次数约为n的1.3次方。

空间复杂度:代码中几乎没有引入额外的辅助空间,因此空间复杂度为O(1)。

(4)、算法特点

不稳定排序、只能用于顺序结构、增量序列中的值不能有除1之外的公因子,且最后一个增量值必须为1.

二、交换排序

1、冒泡排序

(1)、排序思想

冒泡排序可以说是大部分人接触的第一种排序算法。其排序思想也很简单,即每次将一个元素根据其自身大小向数组一侧不断移动。经过一趟排序之后可以确定一个元素的位置。于是对于n个元素的数组,只需要排序n-1趟。

(2)、代码实现

void BubbleSort(int* arr, int n)
{for (int i = 0; i < n - 1; i++) //排序n-1趟{int flag = 0;for (int j = 0; j < n - i - 1; j++) //每趟排序要比较n-i-1次来确定一个数{if (arr[j] > arr[j + 1]){flag = 1;int tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = tmp;}}//经过一趟排序后检验是否有序if (flag == 0){break;}}
}

(3)、复杂度分析

时间复杂度:O(N*2)。

空间复杂度:O(1)。

(4)、算法特点

稳定排序、可用于链式结构、平均性能较差。

2、快速排序

(1)、排序思想

取待排序元素序列中的某个元素作为基准值,按照该基准值将待排序集合分为两个待排序的子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,重复该过程。

1)创建左右指针,确定基准值
2)从右向左找出⽐基准值⼩的数据,从左向右找出⽐基准值⼤的数据,左右指针数据交换,进⼊下次循环

(2)、代码实现

int _QuickSort(int* arr, int left, int right)
{int keyi = left; //默认选最左边为基准值++left;while (left <= right){//左边找大的while (left <= right && arr[left] < arr[keyi]){left++;}//右边找小的while (left <= right && arr[right] > arr[keyi]){right--;}if (left <= right){int tmp = arr[left];arr[left] = arr[right];arr[right] = tmp;left++;right--;}}int tmp = arr[keyi];arr[keyi] = arr[right];arr[right] = tmp;return right;//返回基准值的位置
}void QuickSort(int* a, int left, int right)
{if (left >= right) {return;}int mid = _QuickSort(a, left, right);QuickSort(a, left, mid - 1);QuickSort(a, mid + 1, right);
}

这里可能会有两个问题:

为什么跳出循环后right位置的值一定不大于key也就是为什么此时right指向的位置就是基准值的位置?

因为我们left从左往右找比key大的,left扫描过的地方都是比key小的所以左边都比key小,循环结束后right<left因此right此时指向位置的左边都是小于基准值的,右边同理。

在进行left和right所指向位置的数据交换时,为什么相等也要交换?

相等时也交互会多造成一些损耗。但是在实际复杂场景中,当数组中的数据大量重复时,就不能有效地分割子序列了。

当然也有非递归版本的快速排序,可以借助栈来实现,这里不再详细介绍。

(3)、复杂度分析

时间复杂度:快速排序的趟数取决于递归树的深度。我们可以大致这样看,找基准值正确位置的复杂度为O(N),趟数即划分子序列的次数,即logN,于是大致认为其时间复杂度为O(NlogN),更为精确的计算可以查阅其他资料。当然,如果待排序的序列接近有序,那么快速排序的时间复杂度将接近O(N*2),因为假如是升序的,每次都划分出左子序列为一个值,右子序列为其余全部值。在这种情况下,快速排序的性能会退化。

空间复杂度:对于这种递归版本的快速排序,执行时需要有一个栈来存放相应的数据。最大递归调用次数与递归树深度一致。则最好情况下空间复杂度为O(logN),最坏情况下位O(N)。

(4)、算法特点

不稳定排序、当初始状态接近有序时,平均性能会下降、不适合链式结构。

三、选择排序

1、直接选择排序

(1)、排序思想

每⼀次从待排序的数据元素中选出最⼩(或最⼤)的⼀个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。这个比较简单,我认为比冒泡排序还简单,没有什么可讲的。

(2)、代码实现

void SelectSort(int* arr, int n)
{int begin = 0;int end = n - 1;while (begin < end){int mini = begin, maxi = begin;for (int i = begin; i <= end; i++){if (arr[i] > arr[maxi]){maxi = i;}if (arr[i] < arr[mini]){mini = i;}}//注意处理特殊情况,因为交换有顺序if (begin == maxi){maxi = mini;}swap(&arr[mini], &arr[begin]);swap(&arr[maxi], &arr[end]);++begin;--end;}
}

(3)、复杂度分析

时间复杂度:O(N*2)

空间复杂度:O(1)

(4)、算法特点

稳定排序、可用于链式结构。

2、堆排序

(1)、排序思想

堆排序是一种树形选择排序,将待排序的序列看成顺序存储的完全二叉树结构,利用顺序存储结构,可以很快得到父节点与子节点的对应关系并找到对应结点。所以对于一个建好的堆,在其内部的顺序存储结果中是有序的。所以我们实现堆排序的任务无非两个:

建初堆:如何将一个无序序列建成一个堆

调整堆:去掉堆顶元素,在堆顶元素改变之后,如何调整剩余元素成为一个新的堆。

对于建初堆,要用到调整堆的操作。

调整堆,可以从上向下调整,也可以从下向上调整,我们这里用向下调整实现,以大根堆为例。

//向下调整算法
void AdjustDown(int* arr, int parent, int n)
{int child = parent * 2 + 1;while (child < n){//这里建立大根堆,找最小的孩子然后跟父亲比if (child + 1 < n && arr[child] < arr[child + 1]){child++;}if (arr[child] > arr[parent]){swap(&arr[child], &arr[parent]);parent = child;child = parent * 2 + 1;}else{break;//调整结束}}
}

我们堆排序的整个过程是:不断取出堆顶元素,将其放到合适的位置,然后重新调整堆。

这里我们是大根堆,堆顶元素是整个堆中最大的,那么就是每次和最后一位交换,然后缩小范围重新调整堆。

(2)、代码实现

每次将堆顶元素放到最后一个位置,然后重新调整堆,最终不断变成升序的样子。

void HeapSort(int* arr, int n)
{//child:n-1 parent:(n-1-1)/2for (int i = (n - 1 - 1) / 2; i >= 0; i--) //从最后一个父节点开始逆序向下调整建堆{AdjustDown(arr, i, n);}//此时已经建好大堆int end = n - 1;while (end > 0){swap(&arr[0], &arr[end]); //每次交换将堆顶元素放到合适的位置。AdjustDown(arr, 0, end);end--;}
}

(3)、复杂度分析

时间复杂度:堆排序的时间主要消耗在建初堆和不断的调整堆过程上。在最坏情况下,堆排序的时间复杂度为O(NlogN)。

空间复杂度:空间复杂度为O(1)。

(4)、算法特点

不稳定排序、只能用于顺序结构、建堆所需次数比较多、在元素较少时不宜使用。

四、归并排序

1、归并排序

(1)、排序思想

归并排序是建⽴在归并操作上的⼀种有效的排序算法,是采⽤分治法的⼀个⾮常典型的应⽤。将已有序的⼦序列合并,得到完全有序的序列。即先使每个⼦序列有序,再使⼦序列段间有序。将两个有序表合并成一个有序表的过程称为2路归并。
中间需要创建一个临时数组用来暂存两个序列归并后的结果。

(2)、代码实现

void MergeSort(int* arr, int left, int right, int* tmp)
{if (left >= right){return;}int mid = (left + right) / 2;//根据mid划分为两个子序列[left,mid] [mid+1,right]MergeSort(arr, left, mid, tmp);	MergeSort(arr, mid + 1, right, tmp);//合并[left,mid]和[mid+1,right]int begin1 = left, end1 = mid;int begin2 = mid + 1, end2 = right;int index = begin1;while (begin1 <= end1 && begin2 <= end2){if (arr[begin1] < arr[begin2]){tmp[index++] = arr[begin1++];}else {tmp[index++] = arr[begin2++];}}//循环结束后可能存在某个个序列中的数据没有全部存放到tmp中while (begin1 <= end1){tmp[index++] = arr[begin1++];}while (begin2 <= end2){tmp[index++] = arr[begin2++];}//将数据挪回到arr中for (int i = left; i <= right; i++){arr[i] = tmp[i];}
}

(3)、复杂度分析

时间复杂度:对于n个元素,需进行logn趟归并排序,每趟归并排序关键字比较不超过n,元素移动次数都是n,因此归并排序时间复杂度为O(NlogN)。

空间复杂度:借助了一个额外的辅助存储空间,大小为n,因此空间复杂度为O(N)。

(4)、算法特点

稳定排序、可用于链式结构。

五、基数排序

前面各类排序方法都是建立在关键字比较的基础上,而分配类排序不需要比较关键字的大小,它是根据关键字中各位的值,通过对待排序记录进行若干趟分配与收集来实现排序的,是一种借助于多关键字排序的思想对单关键字进行排序的方法。基数排序就是一种典型的分配类排序。

这里斗胆借用老师上课的课件来展示基数排序的过程。

对这个例子中的十个链式结构的关键码进行排序。

首先创建一个链式结构队列用来进行分配。

第一趟分配首先按每个数的个位来分配,将其个位对10取余后得到余数,然后将其挂在对应的位置上。

接着按挂在上面的顺序将其重新收集为一个关键码链表。

接着进行第二趟分配,这次按十位来看,取每个数的十位对10取余后,按照余数挂在对应的队列位置上,然后进行第二次收集。

可以看到,经过第2趟收集后得到了正确的排序序列。

这种方法正确的原因在于每次分配都针对每一位,得到了不同元素同一位上的相对位置,然后每个位都分配收集完就得到了正确的序列。

对于其时间复杂度:

对于这个例子,基数是10,即那个队列中共有十个选项,排序码位数是2,因为这道题最大的数的位数是2,排序码个数就是元素个数。

每次分配时需要遍历链表,于是分配的时间复杂度是O(N),收集时遍历队列,收集的时间复杂度是O(r)。总共需要分配收集d趟。因此时间复杂度为O(d×(n+r))。

对于空间复杂度:

创建了n个结点的队列,再加上排序码个数,因此空间复杂度是O(n+r)。

基数排序是稳定的,可用于链式结构和顺序结构。

六、总结


http://www.hkcw.cn/article/LDUMqIawJA.shtml

相关文章

【Hot 100】118. 杨辉三角

目录 引言杨辉三角我的解题代码优化优化说明 &#x1f64b;‍♂️ 作者&#xff1a;海码007&#x1f4dc; 专栏&#xff1a;算法专栏&#x1f4a5; 标题&#xff1a;【Hot 100】118. 杨辉三角❣️ 寄语&#xff1a;书到用时方恨少&#xff0c;事非经过不知难&#xff01; 引言 …

【Linux网络】传输层TCP协议

&#x1f308;个人主页&#xff1a;秦jh__https://blog.csdn.net/qinjh_?spm1010.2135.3001.5343 &#x1f525; 系列专栏&#xff1a;https://blog.csdn.net/qinjh_/category_12891150.html 目录 TCP 协议 TCP 协议段格式 确认应答(ACK)机制 超时重传机制 连接管理机制 …

【C语言】C语言经典小游戏:贪吃蛇(上)

文章目录 一、游戏背景及其功能二、Win32 API介绍1、Win32 API2、控制台程序3、定位坐标&#xff08;COORD&#xff09;4、获得句柄&#xff08;GetStdHandle&#xff09;5、获得光标属性&#xff08;GetConsoleCursorInfo&#xff09;1&#xff09;描述光标属性&#xff08;CO…

【Python 算法零基础 4.排序 ⑥ 快速排序】

既有锦绣前程可奔赴&#xff0c;亦有往日岁月可回首 —— 25.5.25 选择排序回顾 ① 遍历数组&#xff1a;从索引 0 到 n-1&#xff08;n 为数组长度&#xff09;。 ② 每轮确定最小值&#xff1a;假设当前索引 i 为最小值索引 min_index。从 i1 到 n-1 遍历&#xff0c;若找到…

Global Securities Markets 第7章知识点总结

一、银行中介的角色定位与核心作用 1. 角色定位&#xff1a;证券流转的核心枢纽 银行作为证券中介&#xff0c;在投资者与证券市场之间扮演三重角色&#xff1a; 资产托管者&#xff1a;负责证券实物或电子形式的安全保管&#xff08;如美国道富银行托管全球ETF资产&#xf…

docker B站学习

镜像是一个只读的模板&#xff0c;用来创建容器 容器是docker的运行实例&#xff0c;提供了独立可移植的环境 https://www.bilibili.com/video/BV11L411g7U1?spm_id_from333.788.videopod.episodes&vd_sourcee60c804914459274157197c4388a4d2f&p3 目录挂载 尚硅谷doc…

我爱学算法之—— 前缀和(上)

一、【模板】前缀和 题目解析 这道题&#xff0c;给定一个长度为n的数组&#xff0c;和m次询问&#xff1b; 每一次询问给出两个整数l和r&#xff0c;让我们求出区间[l , r]中所有数的和&#xff0c;然后输出。 算法思路 这道题暴力解法&#xff1a; 首先是m次查询&#xff0…

RK3568+LINUX + CODESYS带授权+实时系统,同时开自己的视觉应用

RK3568LINUX CODESYS带授权实时系统,同时开自己的视觉应用&#xff0c;让你即可以选择几个核心跑codesys&#xff0c;又可以选几个核心跑自定义的应用。 基于RK3568处理器构建的工业控制与视觉融合解决方案&#xff0c;可通过以下技术架构实现&#xff1a; 一、硬件平台核心配…

Boss直聘批量投简历工具使用教程

Boss直聘批量投简历工具使用教程 为什么boss有活跃度显示 但是不给我们筛选&#xff0c;推荐的十个七个都是十天半个月不在线的岗位hr 我投递了有意思吗 不就是在浪费我们的精力 同时也浪费了每天的投递次数 第二个问题 我想做一个批量投简历工具 或者&#xff08;自动投简历…

写个简单的浏览器插件,收藏网页内容

要求&#xff1a; 此时我觉得都ok了。请帮我写一篇文章来介绍这个项目。从最初的要求到最后实现的效果。要求篇幅不要太长&#xff0c;语言幽默有趣&#xff0c; 平易近人&#xff0c; 有吸引力。重点介绍的是起因&#xff0c;即&#xff0c;需求和起因增加篇幅&#xff0c;其…

Attention注意力机制

Attention核心思想 作用&#xff1a;处理时序问题 核心思想&#xff1a;处理序列数据时&#xff0c;网络应该更加关注输入中重要的部分&#xff0c;忽略不重要的部分。 要怎么做到&#xff1f; 通过学习不同部分的权重&#xff0c;将输入的序列中的重要部分显式加权&#xf…

CRC 原理概述

CRC 原理概述 摘要&#xff1a;循环冗余校验&#xff08;CRC, Cyclic Redundancy Check&#xff09;是一种基于多项式除法&#xff08;modulo-2&#xff09;的差错检测码。它将数据视为一个二进制多项式 D(x)&#xff0c;生成多项式为 G(x)&#xff0c;通过“除法”得到的余数 …

风控研发大数据学习路线

在如今信息爆炸时代&#xff0c;风控系统离不开大数据技术的支撑&#xff0c;大数据技术可以帮助风控系统跑的更快&#xff0c;算的更准。因此&#xff0c;风控技术研发需要掌握大数据相关技术。然而大数据技术栈内容庞大丰富&#xff0c;风控研发同学很可能会面临以下这些痛点…

美载有2.5亿只蜜蜂卡车翻车 养蜂专家紧急救援

美国华盛顿州近日发生了一起罕见事故,一辆载有约2.5亿只蜜蜂的半挂卡车在行驶途中翻覆,导致大量蜜蜂倾巢而出。事故地点靠近加拿大边境,距离温哥华仅约48公里。当地警方迅速封锁道路,并呼吁民众远离蜂群,同时紧急召集养蜂专家到场协助处理。怀特康郡警局发布公告称,事故发…

MySQL垂直分库(基于MyCat)

参考资料&#xff1a; 参考视频 参考博客 Mycat基本部署 视频参考资料&#xff1a;链接: https://pan.baidu.com/s/1xT_WokN_xlRv0h06b6F3yg 提取码: aag3 概要&#xff1a; 本文的垂直分库&#xff0c;全部是基于前文部署的基本架构进行的 垂直分库&#xff1a; 垂直分库…

MySQL 核心知识整理【一】

一、MySQL存储引擎对比&#xff1a;InnoDB vs MyISAM 在使用MySQL时&#xff0c;选择合适的存储引擎对性能影响很大。最常见的两个引擎是 InnoDB 和 MyISAM&#xff0c;它们各自的设计目标不同&#xff0c;适用场景也不一样。 事务与数据安全性方面&#xff0c;InnoDB 支持事…

MySQL下载安装配置环境变量

MySQL下载安装配置环境变量 文章目录 MySQL下载安装配置环境变量一、安装MySQL1.1 下载1.2 安装 二、查看MySQL服务是否启动三、配置环境变量四、验证 一、安装MySQL 1.1 下载 官网社区版&#xff08;免费版&#xff09;&#xff1a;https://dev.mysql.com/downloads/mysql/ …

火语言UI组件--文件夹对话框

【组件功能】&#xff1a;选择单个或多个文件的对话框。 样式预览 设置 基础设置 属性名称属性释义输入值类型标题(title)对话框的标题字符串类型默认路径(defaultPath)对话框的默认展示路径字符串类型多选(multiSelections)是否允许多选布尔型(true / false)显示隐藏文件(s…

海底三维可视化平台

1. 摘要 本文作者为视觉分析构建了一个真实海底的“虚拟世界”。在3D环境中导入底部轮廓。在该模型中&#xff0c;通过地震反射获得的海床地层剖面被数字化为离散点&#xff0c;并用克里金算法进行插值&#xff0c;以在每个地层中产生均匀的网格。然后在每一层构建 Delaunay三…

联合国裁员计划曝光 预算削减20%

多家媒体29日披露的联合国内部文件显示,联合国秘书处计划削减20%预算,并裁员6900人,约占员工总数的20%。根据法新社和路透社获取的联合国内部备忘录,负责财政事务的联合国助理秘书长钱德拉穆利拉马纳坦本周向各部门负责人发出信函,要求执行联合国秘书长安东尼奥古特雷斯提…