各种内部排序算法的比较及应用(插入排序、交换排序、选择排序、归并排序、基数排序)

devtools/2024/12/22 20:30:50/

目录

内部排序

前言

1.内部算法>排序算法的比较

1.1各种算法>排序算法的特点、比较和适用场景

1.2算法>排序算法的稳定性判断及改进 

1.3更适合采用顺序存储的算法>排序算法

1.4根据排序的中间过程判断所采用的算法>排序算法

1.5各种算法>排序算法的性质

2.内部算法>排序算法的应用

2.1选取算法>排序算法时需要考虑的因素

2.2算法>排序算法小结

 3.内部排序各种算法性能分析

3.1直接插入排序

3.2折半插入排序

3.3希尔排序

3.4冒泡排序

3.5快速排序

3.6简单选择排序

3.7堆排序

3.8归并排序

3.9基数排序

3.10计数排序 


内部排序

前言

这篇文章总结了所有内部排序的适用场景,性质特点,以及空间和时间复杂度的考量;

前半部分是对内部排序的总结,后半部分是对所有内部排序的具体性能分析;

1.内部算法>排序算法的比较

  • 前面讨论的算法>排序算法很多,对各种算法>排序算法的比较是考研常考的内容。
  • 一般基于五个因素进行对比:时间复杂度、空间复杂度、稳定性、适用性和过程特征

1.1各种算法>排序算法的特点、比较和适用场景

从时间复杂度看


简单选择排序、直接插入排序和冒泡排序平均情况下的时间复杂度都为O(n²),且实现过程也较为简单,但直接插入排序和冒泡排序最好情况下的时间复杂度可以达到O(n),而简单选择排序则与序列的初始状态无关。


希尔排序作为插入排序的拓展,对较大规模的数据都可以达到很高的效率,但目前未得出其精确的渐近时间。


堆排序利用了一种称为堆的数据结构,可以在线性时间内完成建堆,且在 O(nlog₂n)内完成排序过程。


快速排序基于分治的思想,虽然最坏情况下的时间复杂度会达到 O(n²),但快速排序的平均性能可以达到 O(nlog₂n),在实际应用中常常优于其他算法>排序算法


归并排序同样基于分治的思想,但由于其分割子序列与初始序列的排列无关,因此它的最好、最坏和平均时间复杂度均为 O(nlog₂n)。

从空间复杂度看


简单选择排序、插入排序、冒泡排序、希尔排序和堆排序都仅需借助常数个辅助空间。


快速排序需要借助一个递归工作栈,平均大小为 O(log₂n),当然在最坏情况下可能会增长到 O(n)。


二路归并排序在合并操作中需要借助较多的辅助空间用于元素复制,大小为 O(n),虽然有方法能克服这个缺点,但其代价是算法会很复杂而且时间复杂度会增加。

1.2算法>排序算法的稳定性判断及改进 

从稳定性看:


插入排序、冒泡排序、归并排序和基数排序是稳定的算法>排序算法


简单选择排序、快速排序、希尔排序和堆排序都是不稳定的算法>排序算法


平均时间复杂度为 O(nlog₂n)的稳定算法>排序算法只有归并排序,对于不稳定的算法>排序算法,只需举出一个不稳定的实例即可。


对于算法>排序算法的稳定性,读者应能从算法本身的原理上去理解,而不应拘泥于死记硬背。

1.3更适合采用顺序存储的算法>排序算法

从适用性看:


折半插入排序、希尔排序、快速排序和堆排序适用于顺序存储。


直接插入排序、冒泡排序、简单选择排序、归并排序和基数排序既适用于顺序存储,又适用于链式存储。

1.4根据排序的中间过程判断所采用的算法>排序算法

从过程特征看:


采用不同的算法>排序算法,在一趟或几趟处理后的排序结果通常是不同的,考研题中经常出现给出一个待排序的初始序列和已部分排序的序列,问其采用何种算法>排序算法


这就要对各类算法>排序算法的过程特征十分熟悉,如冒泡排序、简单选择排序和堆排序在每趟处理后都能产生当前的最大值或最小值,


快速排序一趟处理至少能确定一个元素的最终位置等。


下表列出了各种算法>排序算法的时空复杂度和稳定性情况,其中空间复杂度仅列举了平均情况的复杂度,因为希尔排序的时间复杂度依赖于增量函数,所以无法准确给出其时间复杂度。

1.5各种算法>排序算法的性质

算法种类时间复杂度空间复杂度是否稳定
最好情况平均情况最坏情况

直接插入排序

O(n)O(n²)O(n²)O(1)
冒泡排序O(n)O(n²)O(n²)O(1)
简单选择排序O(n²)O(n²)O(n²)O(1)
希尔排序O(1)
快速排序O(nlog₂n)O(nlog₂n)O(n²)O(log₂n)
堆排序O(nlog₂n)O(nlog₂n)O(nlog₂n)O(1)
二路归并排序O(nlog₂n)O(nlog₂n)O(nlog₂n)O(n)
基数排序O(d(n+r))O(d(n+r))O(d(n+r))O(r)

2.内部算法>排序算法的应用

2.1选取算法>排序算法时需要考虑的因素

  1. 待排序的元素个数 n。
  2. 待排序的元素的初始状态。
  3. 关键字的结构及其分布情况。
  4. 稳定性的要求。
  5. 存储结构及辅助空间的大小限制等。

2.2算法>排序算法小结

1)

  • 若 n较小,可采用直接插入排序或简单选择排序。
  • 由于直接插入排序所需的记录移动次数较简单选择排序的多,因此当记录本身信息量较大时,用简单选择排序较好。

2)

  • 若n较大,应采用时间复杂度为 O(nlog₂n)的算法>排序算法:快速排序、堆排序或归并排序。
  • 当待排序的关键字随机分布时,快速排序被认为是目前基于比较的内部算法>排序算法中最好的算法
  • 堆排序所需的辅助空间少于快速排序,且不会出现快速排序可能的最坏情况,这两种排序都是不稳定的。
  • 若要求稳定且时间复杂度为 O(nlog₂n),可选用归并排序。

3)

  • 若文件的初始状态已按关键字基本有序,则选用直接插入或冒泡排序为宜。


4)

  • 在基于比较的算法>排序算法中,每次比较两个关键字的大小之后,仅出现两种可能的转移,因此可以用一棵二叉树来描述比较判定过程,
  • 由此可以证明:当文件的n个关键字随机分布时,任何借助于“比较”的算法>排序算法,至少需要 O(nlog₂n)的时间。

5)

  • 若n很大,记录的关键字位数较少且可以分解时,采用基数排序较好。

6)

  • 当记录本身信息量较大时,为避免耗费大量时间移动记录,可用链表作为存储结构。

 3.内部排序各种算法性能分析

3.1直接插入排序

直接插入算法>排序算法性能分析如下:

空间效率:仅使用了常数个辅助单元,因而空间复杂度为O(1)


时间效率:在排序过程中,向有序子表中逐个地插入元素的操作进行了n-1趟,

  • 每趟操作都分为比较关键字和移动元素,而比较次数和移动次数取决于待排序表的初始状态。
  • 在最好情况下,表中元素已经有序,此时每插入一个元素,都只需比较一次而不用移动元素,因而时间复杂度为 O(n)。
  • 在最坏情况下,表中元素顺序刚好与排序结果中的元素顺序相反(逆序),总的比较次数达到最大,总的移动次数也达到最大,总的时间复杂度为O(n²)。
  • 平均情况下,考虑待排序表中元素是随机的,此时可以取上述最好与最坏情况的平均值作为平均情况下的时间复杂度,总的比较次数与总的移动次数均约为n²/4。
  • 因此,直接插入算法>排序算法的时间复杂度为 O(n²)

稳定性:因为每次插入元素时总是从后往前先比较再移动,所以不会出现相同元素相对位置发生变化的情况,即直接插入排序是一个稳定的算法>排序算法


适用性直接插入排序适用于顺序存储和链式存储的线性表,采用链式存储时无须移动元素

3.2折半插入排序

折半插入算法>排序算法性能分析如下:

空间效率:在排序过程中只需要额外使用常数级别的额外空间,不随待排序数组的大小而变化,所以空间复杂度为O(1)。


时间效率:折半插入排序仅减少了比较元素的次数,该比较次数与待排序表的初始状态无关,仅取决于表中的元素个数n;

而元素的移动次数并未改变它依赖于待排序表的初始状态。

因此,折半插入排序的时间复杂度仍为 O(n²),但对于数据量不很大的排序表,折半插入排序往往能表现出很好的性能。


稳定性:折半插入排序是一种稳定算法>排序算法


适用性:折半插入排序仅适用于顺序存储的线性表

3.3希尔排序

希尔算法>排序算法性能分析如下:

空间效率:仅使用了常数个辅助单元,因而空间复杂度为O(1)


时间效率:因为希尔排序的时间复杂度依赖于增量序列的函数,这涉及数学上尚未解决的难题,所以其时间复杂度分析比较困难。

  • 当n在某个特定范围时,希尔排序的时间复杂度约为O(n^{1.3})
  • 在最坏情况下希尔排序的时间复杂度为O(n²)。

稳定性:当相同关键字的记录被划分到不同的子表时,可能会改变它们之间的相对次序,

因此希尔排序是一种不稳定的算法>排序算法。例如,图82中49与49的相对次序已发生了变化。


适用性:希尔排序仅适用于顺序存储的线性表。

3.4冒泡排序

冒泡排序的性能分析如下:

空间效率:仅使用了常数个辅助单元,因而空间复杂度为O(1)


时间效率:当初始序列有序时,显然第一趟冒泡后 flag 依然为 false(本趟没有元素交换),从而直接跳出循环,比较次数为n-1,移动次数为0,

  • 从而最好情况下的时间复杂度为 O(n)
  • 当初始序列为逆序时,需要进行n-1趟排序,第 i 趟排序要进行n-i次关键字的比较,而且每次比较后都必须移动元素3次来交换元素位置。
  • 这种情况下, 
  • 比较次数=\sum_{i=1}^{n-1}(n-i)=\frac{n(n-1)}{2}
  • 移动次数=\sum_{i=1}^{n-1}3(n-i)=\frac{3n(n-1)}{2}
  • 从而,最坏情况下的时间复杂度为 O(n²),平均时间复杂度为 O(n²)

稳定性:由于 i>j 且 A[i]=A[j] 时,不会发生交换,因此冒泡排序是一种稳定的算法>排序算法


适用性:冒泡排序适用于顺序存储和链式存储的线性表

3.5快速排序

快速算法>排序算法性能分析如下:

  • 空间效率:由于快速排序是递归的,因此需要借助一个递归工作栈来保存每层递归调用的必要信息,其容量与递归调用的最大层数一致。
  • 最好情况下为 O(log₂n);
  • 最坏情况下,要进行n-1次递归调用,因此栈的深度为 O(n);
  • 平均情况下,栈的深度为 O(log₂n)。

  • 时间效率快速排序的运行时间与划分是否对称有关,快速排序的最坏情况发生在两个区域分别包含n-1个元素和0个元素时,这种最大限度的不对称性若发生在每层递归上,即对应于初始排序表基本有序或基本逆序时,就得到最坏情况下的时间复杂度为 O(n²)。

  • 有很多方法可以提高算法的效率:一种方法是尽量选取一个可以将数据中分(对称平分)的枢轴元素,如从序列的头尾及中间选取三个元素,再取这三个元素的中间值作为最终的枢轴元素;或者随机地从当前表中选取枢轴元素,这样做可使得最坏情况在实际排序中几乎不会发生。

  • 在最理想的状态下,即 Partition()能做到最平衡的划分,得到的两个子问题的大小都不可能大于 n/2,在这种情况下,快速排序的运行速度将大大提升,此时,时间复杂度为 O(nlog₂n)。
  • 好在快速排序平均情况下的运行时间与其最佳情况下的运行时间很接近,而不是接近其最坏情况下的运行时间。
  • 快速排序是所有内部算法>排序算法中平均性能最优的算法>排序算法

  • 稳定性:在划分算法中,若右端区间有两个关键字相同,且均小于基准值的记录,则在交换到左端区间后,它们的相对位置会发生变化,即快速排序是一种不稳定的算法>排序算法
  • 例如,表L=(3,2,2),经过一趟排序后L={2,2,3},最终排序序列也是L={2,2,3},显然,2与2的相对次序已发生了变化。

  • 适用性:快速排序仅适用于顺序存储的线性表

3.6简单选择排序

简单选择算法>排序算法性能分析如下:

空间效率:仅使用常数个辅助单元,所以空间效率为O(1)


时间效率:从上述伪码中不难看出,

  • 在简单选择排序过程中,元素移动的操作次数很少,不会超过 3(n-1)次,最好的情况是移动0次,此时对应的表已经有序;
  • 但元素间比较的次数与序列的初始状态无关,始终是 n(n-1)/2 次,因此时间复杂度始终是 O(n²)

稳定性

  • 在第 i 趟找到最小元素后,和第 i 个元素交换,可能会导致第 i 个元素与含有相同关键字的元素的相对位置发生改变。
  • 例如,表 L={2,2,1},经过一趟排序后 L={1,2,2),最终排序序列也是L={1,2,2},显然,2与2的相对次序已发生变化。
  • 因此,简单选择排序是一种不稳定的算法>排序算法

适用性:简单选择排序适用于顺序存储和链式存储的线性表,以及关键字较少的情况

3.7堆排序

算法>排序算法性能分析如下:

空间效率:仅使用了常数个辅助单元,所以空间复杂度为O(1)


时间效率:建堆时间为 O(n),之后有n-1次向下调整操作,每次调整的时间复杂度为 O(h),

所以在最好、最坏和平均情况下,堆排序的时间复杂度为O(nlog₂n)


稳定性:进行筛选时,有可能把后面相同关键字的元素调整到前面,所以堆算法>排序算法是一种不稳定的算法>排序算法

  • 例如,表L={1,2,2),构造初始堆时可能将2交换到堆顶,此时L={2,1,2},最终排序序列为 L={1,2,2},显然,2与2的相对次序已发生变化。

适用性:堆排序仅适用于顺序存储的线性表

3.8归并排序

二路归并算法>排序算法性能分析如下:

空间效率:Merge()操作中,辅助空间刚好为n个单元,因此算法空间复杂度为O(n)。


时间效率:每趟归并的时间复杂度为 O(n),共需进行\left \lceil log_{2}n \right \rceil趟归并,因此算法的时间复杂度为O(nlog_{2}n)


稳定性:由于 Merge()操作不会改变相同关键字记录的相对次序,因此二路归并算法>排序算法是一种稳定的算法>排序算法


适用性:归并排序适用于顺序存储和链式存储的线性表

3.9基数排序

基数算法>排序算法性能分析如下。

空间效率:一趟排序需要的辅助存储空间为r (r个队列:r个队头指针和r个队尾指针),但以后的排序中会重复使用这些队列,所以基数排序的空间复杂度为O(r)


时间效率

  • 基数排序需要进行 d趟“分配”和”收集”操作。
  • 一趟分配需要遍历所有关键字,时间复杂度为 O(n);
  • 一趟收集需要合并r个队列,时间复杂度为 O(r)。
  • 因此基数排序的时间复杂度为 O(d(n+r)),它与序列的初始状态无关

稳定性:每一趟分配和收集都是从前往后进行的,不会交换相同关键字的相对位置,因此基数排序是一种稳定的算法>排序算法


适用性:基数排序适用于顺序存储和链式存储的线性表。 

3.10计数排序 

计数算法>排序算法性能分析如下。

空间效率:计数排序是一种用空间换时间的做法。

  • 输出数组的长度为n;
  • 辅助的计数数组的长度为 k,空间复杂度为 O(n+k)。
  • 若不把输出数组视为辅助空间,则空间复杂度为 O(k)。

时间效率

  • 上述代码的第1个和第3个for 循环所花的时间为 O(k),
  • 第2个和第4个 for 循环所花的时间为 O(n),
  • 总时间复杂度为 O(n+ k)。
  • 因此,当 k=O(n) 时,计数排序的时间复杂度为O(n);
  • 但当 k>O(nlog₂n)时,其效率反而不如一些基于比较的排序(如快速排序、堆排序等)。

稳定性:上述代码的第4个 for 循环从后往前遍历输入数组,相同元素在输出数组中的相对位置不会改变,因此计数排序是一种稳定的算法>排序算法


适用性:计数排序更适用于顺序存储的线性表。计数排序适用于序列中的元素是整数且元素范围(0~k-1)不能太大,否则会造成辅助空间的浪费。

 


http://www.ppmy.cn/devtools/48438.html

相关文章

Java 数据类型 -- Java 语言的 8 种基本数据类型、字符串与数组

大家好,我是栗筝i,这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 004 篇文章,在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验,并希望进…

cdh中的zookeeper怎么配置zoo.cfg

你手动改了zoo.cfg目录是不会生效的,因为是cdh在管控,所以只能通过cdh修改。 首先打开cdh。 xxx:7180 点击zookeeper 选配置,然后选高级 在右边找,有一个就是zoo.cfg,可以点击右边的感叹号。然后在里面编辑的就会直…

改进YOLO系列 | YOLOv5/v7 引入 Dynamic Snake Convolution | 动态蛇形卷积

改进YOLO系列:动态蛇形卷积(Dynamic Snake Convolution,DSC) 简介 YOLO系列目标检测算法以其速度和精度著称,但对于细长目标例如血管、道路等,其性能仍有提升空间。 动态蛇形卷积(DSC&#xf…

React+TS前台项目实战(五)-- 全局常用组件Link封装+使用Omit定义类型

文章目录 前言Link组件1. 功能分析2. 代码注释说明3. 使用方式 总结 前言 接下来的几篇文章,将主要封装全局常用组件,以便于后续编写页面的简易和维护性的提高。本文将主要讲述跳转组件的封装。 Link组件 1. 功能分析 (1)国际化…

go匿名函数

【1】Go支持匿名函数,如果我们某个函数只是希望使用一次,可以考虑使用匿名函数 【2】匿名函数使用方式: (1)在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次(用的多) &am…

编程的m09:深入解读这一神秘指令的四大方面、五大细节、六大应用场景及七大挑战

编程的m09:深入解读这一神秘指令的四大方面、五大细节、六大应用场景及七大挑战 在编程的世界中,各种指令和代码如同魔法咒语,它们被赋予特定的含义和功能,用以操控计算机完成各种任务。然而,对于初学者或是不熟悉某个…

Eureka和Nacos有哪些区别?

Eureka和Nacos都能起到注册中心的作用,用法基本类似。但还是有一些区别的,例如: Nacos支持配置管理,而Eureka则不支持。 而且服务注册发现上也有区别,我们来做一个实验: 我们停止user-service服务&#x…

京准电钟|基于纳秒级的GPS北斗卫星授时服务器

京准电钟|基于纳秒级的GPS北斗卫星授时服务器 京准电钟|基于纳秒级的GPS北斗卫星授时服务器 你有没有思考过这样一个问题:火车站内,熙熙攘攘,旅客排队进站、列车停靠发车,一切井然有序。一旦有个别时间出现…