贰.1.1 十大排序算法

先看一张图表总结个中排序算法对比(点击可看大图)。

或看下面这张更经典的

01. 冒泡排序

冒泡排序是一种简单的排序算法,它重复地遍历要排序的数组,比较相邻的两个元素,如果它们的顺序错误就交换它们的位置。这个过程会一直重复,直到没有再需要交换的元素,也就是说该数组已经排序完成。

因为嵌套两个for循环,所以冒泡排序的时间复杂度为 O(N2)O(N^2),空间复杂度为 O(1)O(1)(只需要一个临时变量来交换元素)。

动画演示

冒泡排序动画演示

02. 选择排序

假设数组A里面有N个项,选择排序是这样的:数组A第i个项记作 AiA_i,第(i+1)~(N-1)项中的最小(最大的情况也是相似的逻辑)值记作 AmA_m ,然后比较它俩大小,如果 AmA_m 小于 AiA_i ,交换位置……直到把整个数组处理一遍。代码如下:

可以看出选择排序也是一外一内两个for循环,因此时间复杂度也是 O(N2)O(N^2),空间复杂度为 O(1)O(1)。但是选择排序与冒泡排序差别地方在于:

  • 选择排序的位置交换是在第二个for循环之外,第一个for循环之内,因此选择排序交换位置最多N次,而冒泡排序最多可能 N2N^2 次。所以大多数情况下,选择排序比冒泡排序效率好一些。

动画演示

选择排序动画演示

03. 插入排序

插入排序我们打扑克时给手中牌排序的思路一样,刚开始左手上一堆乱序的牌,我们把牌往手的右边挪一挪,把手的左边空出一点位置来,然后在右手乱牌中抽一张出来,插入到左边,再抽一张出来,插入到左边……每次插入都插入到左边合适的位置,时刻保持左边的牌是有序的,直到右边的牌抽完,则排序完毕。

代码实现如下:

从代码里我们可以看出,如果找到了合适的位置,就不会再进行比较了,就好比右手里抽出的一张牌本身就比我左手里的牌都小,那么我只需要直接放在左手靠边位置就行了,不用一张一张去移动牌腾出位置插入到中间。

所以说,最好情况的时间复杂度是 O(N)O(N) ,最坏情况的时间复杂度是 O(N2)O(N^2) ,然而时间复杂度这个指标看的是最坏的情况,而不是最好的情况,所以插入排序的时间复杂度也是 O(N2)O(N^2)。空间复杂度为 O(1)O(1)

动画演示

插入排序动画演示

04. 堆排序

堆的特性:

  • 必须是完全二叉树;

  • 任一结点的值是其子树所有结点的最大值或最小值。 最大值时,称为"最大堆",也称大顶堆; 最小值时,称为"最小堆",也称小顶堆。

堆排序主要用到最大堆/最小堆的删除操作,也即根节点已经是最大/小的值,排序的话,只需要把根结点拿(删)掉,放入有序的新数组里,然后用下沉算法处理剩余的结点以便组成新的最大堆/最小堆……如此循环。

所谓下沉算法,拿最小堆来举例说(最大堆同理),就是把完全二叉树根结点R和该树第二层左右子结点的值比较,如果大,结点就互换位置("下沉"),以此逐层递归,直到处理完整棵树,此时,根节点值最小。

草稿图可以这么画,然后归纳、设计代码,更方便理解。

堆排序的时间复杂度是 O(NlogN)O(NlogN),空间复杂度为 O(1)O(1)(原地排序)。

动画演示

堆排序动画演示

05. 归并排序

归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

归并排序的核心思想是:

  1. 将数组分成两半

  2. 递归地对两半进行归并排序

  3. 将两个有序数组合并成一个有序数组

归并排序的时间复杂度是 O(NlogN)O(NlogN),空间复杂度是 O(N)O(N)。它的优点是稳定排序,时间复杂度稳定,缺点是需要额外的空间。

动画演示

归并排序动画演示

06. 快速排序

快速排序其实是分治法,将待排序数组里的项和基准数对比,比基准数大的放在一边,小的放另一边,然后再对左右两边的子数组重复使用这个思路,直到整个数组排序完毕。

时间复杂度最好的情况是 O(NlogN)O(NlogN),最差的情况是 O(N2)O(N^2)。算法分析:

  • 当分区选取的基准元素为待排序元素中的最大或最小值时,为最差的情况,时间复杂度和直接插入排序的一样,移动次数达到最大值

    Cmax = 1+2+...+(n-1) = n*(n-1)/2 = O(n2) 此时时间复杂为 O(N2)O(N^2)

  • 当分区选取的基准元素为待排序元素中的"中值",为最好的情况,时间复杂度为 O(NlogN)O(NlogN)

快速排序的空间复杂度为 O(logN)O(logN)(递归调用栈的深度),最坏情况下为 O(N)O(N)

动画演示

快速排序动画演示

07. 希尔排序

希尔排序是插入排序的一种改进版本,也称为缩小增量排序。它通过比较相距一定间隔的元素来工作,各趟比较所用的距离随着算法的进行而减小,直到最后一趟比较相邻元素。

希尔排序的核心思想是:

  1. 设定一个初始间隔gap

  2. 按照gap分组,对每组使用插入排序

  3. 减小gap,重复步骤2

  4. 当gap=1时,就是普通的插入排序

希尔排序的时间复杂度取决于间隔序列的选择,最坏情况下是 O(N2)O(N^2),但在实际应用中通常比 O(N2)O(N^2) 要好。空间复杂度为 O(1)O(1)。它的优点是对于中等大小的数组表现良好,且代码相对简单。

动画演示

希尔排序动画演示

08. 计数排序

计数排序是一种非比较排序算法,它通过统计每个元素出现的次数来进行排序。计数排序适用于已知数据范围的整数排序,特别是当数据范围不大时效率很高。

计数排序的核心思想是:

  1. 统计每个元素出现的次数

  2. 根据统计结果重构原数组

计数排序的时间复杂度是 O(N+K)O(N + K),其中K是数据范围。空间复杂度为 O(N+K)O(N + K)。当数据范围不大时,计数排序的效率很高,但需要额外的空间来存储计数数组。

动画演示

计数排序动画演示

09. 桶排序

桶排序是计数排序的升级版,它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。桶排序的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶中,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。

桶排序的核心思想是:

  1. 将数据分到有限数量的桶中

  2. 对每个桶中的数据进行排序

  3. 将桶中的数据按顺序合并

桶排序的时间复杂度是 O(N+K)O(N + K),其中K是桶的数量。空间复杂度为 O(N+K)O(N + K)。当数据分布均匀时,桶排序的效率很高,但需要额外的空间来存储桶。

动画演示

桶排序动画演示

10. 基数排序

基数排序是一种非比较排序算法,它通过将整数按位数切割成不同的数字,然后按每个位数分别比较来进行排序。基数排序适用于整数排序,特别是当数据范围很大但位数相对较少时。

基数排序的核心思想是:

  1. 从最低位开始,按每一位进行排序

  2. 使用稳定的排序算法(如计数排序)对每一位进行排序

  3. 重复步骤1-2,直到最高位

基数排序的时间复杂度是 O(D(N+K))O(D(N + K)),其中D是最大数的位数,K是基数(通常是10)。空间复杂度为 O(N+K)O(N + K)。基数排序适用于整数排序,特别是当数据范围很大但位数相对较少时效率很高。

动画演示

基数排序动画演示

总结

以上十种排序算法各有特点:

按时间复杂度分类:

  • 冒泡排序、选择排序、插入排序:时间复杂度 O(N2)O(N^2),适合小数据量

  • 希尔排序:改进的插入排序,时间复杂度介于 O(N)O(N)O(N2)O(N^2) 之间

  • 堆排序、归并排序、快速排序:时间复杂度 O(NlogN)O(NlogN),适合大数据量

  • 计数排序、桶排序、基数排序:时间复杂度 O(N+K)O(N + K),适合特定数据分布

按空间复杂度分类:

  • 原地排序O(1)O(1)):冒泡排序、选择排序、插入排序、堆排序、希尔排序

  • 需要额外空间O(N)O(N) 或更高):归并排序、快速排序、计数排序、桶排序、基数排序

按稳定性分类:

  • 稳定排序:冒泡排序、插入排序、归并排序、计数排序、桶排序、基数排序

  • 不稳定排序:选择排序、堆排序、快速排序、希尔排序

在实际应用中,需要根据数据规模、数据特征、稳定性要求、内存限制等因素来选择合适的排序算法。

最后更新于

这有帮助吗?