各种排序算法性能特点

算法 是否稳定 是否原地排序 时间复杂度 空间复杂度 备注
选择排序 N^2 1
插入排序 介于N和N^2之间 1 取决于元素的输入情况
希尔排序 NlogN? N^6/5? 1
快速排序 NlogN logN 运行效率由概率提供保证
归并排序 NlogN N
堆排序 NlogN 1

算法简述

1.选择排序

首先,找到数组中最小的那个元素,其次,将它和数组的第一个元素交换位置.再次,在剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置.如此往复,即可将数组进行排序.
特点:(1)运行时间和输入无关; (2)数据移动是最少的

2.插入排序

通常人们整理桥牌的方法是一张一张的来,将每一张牌插入到其它已经有序的牌中的适当位置.在计算机实现中,为了给要插入的元素腾出空间,我们需要把插入位置处后面的元素都向右移动一位,然后将新元素插入空出的位置处.
特点:插入排序对"部分有序数组"很有效,当倒置的数量很少时,该算法可能比任何算法都要快.
部分有序数组典型示例:
  数组中每个元素离它的最终位置都不远
  一个有序的大数组接一个小数组
  数组中只有几个元素位置不正确

3.希尔排序

希尔排序实际上是对插入排序的改进,后者对于大规模乱序数组插入很慢,因为它只会交换相邻的元素,因此元素只能一点一点地从数组一端移动到另一端.而希尔排序的思想是使数组中任意间隔为h的元素都是有序的(间隔为h的元素排序采用的是插入排序),这个间隔h在每次循环之后都会不断减小(减小的规则是按递增序列1/2(3^k-1),从N/3开始减小到1),直到h=1,此时整个数组完成排序.
特点:有经验的程序员有时会选择希尔排序,因为对于中等大小的数组它的运行时间是可以接受的.它的代码量很小,且不需要使用额外的内存空间.下面的几种更高效的算法,除了对于很大的N,它们可能只会比希尔排序快两倍,而且更复杂.如果你需要解决一个排序问题而又没有系统排序函数可用(例如直接接触硬件或是运行于嵌入式系统中的代码),可以先用希尔排序,然后再考虑是否值得将它替换为更为复杂的排序算法.

4.快速排序

快速排序将数组排序的方式是当两个子数组都有序时整个数组也就自然有序了,其大致过程如下,首先(1)打乱数组(使切分能尽可能的平衡),(2)切分(使切分元素左边的值都不大于它,而右边的值都不小于它),(3)将左半部分排序(可继续递归切分,当左半部分较小时,可直接采用插入排序),(4)将右半部分排序(如3同),最后即可获得排序结果.
常见排序算法性能分析总结
特点:快速排序最引人注目的地方包括它的原地排序,且将长度为N的数组排序所需的时间和NlogN成正比.这在已学过的排序算法都无法将这两个优点结合.它的缺点是非常脆弱,在切分不平衡时这个算法可能会极为低效,最多需要N^2/2比较,但通过随机打乱数组可以预防这个问题,这个策略对应上图中的"打乱"操作.

5.归并排序

归并排序过程如下图所示,首先将原数组递归的分成很多小规模数组,然后将小规模有序数组两两归并成一个较大的数组,再将较大的数组两两归并成一个更大的数组,直至归并成一个数组,即完成对原数组的排序.
常见排序算法性能分析总结
特点:归并排序是应用高效算法设计中"分治思想"的最典型的一个例子,是一种渐进最优的基于比较排序的算法.其最吸引人的性质是它能够保证将任意长度为N的数组排序所需的时间和NlogN成正比,它的主要缺点是它所需的额外空间和N成正比.

6.堆排序

堆排序利用基于堆的优先队列将所有元素插入一个查找最小元素的优先队列,然后再重复删除最小元素的操作来将它们按顺序删除,按删除顺序保存删除的元素,即实现数组的排序.其主要包括如下两个过程,堆的构造和下沉排序.
常见排序算法性能分析总结
特点:堆排序在排序复杂性研究中有着重要的地位,因为它是我们所知的唯一能够同时最优地利用空间和时间的方法-在最坏的情况下也能保证使用~2NlogN次比较和恒定的额外空间.当空间十分紧张的时候(例如在嵌入式系统或低成本的移动设备中)它很流行,因为它只用几行就能实现较好的性能.但现代系统的许多应用很少使用它,因为它无法利用缓存.数组元素很少和相邻的其他元素进行对比,因此缓存未命中的次数要远远高于大多数比较都在相邻元素间进行的算法.

疑问

1.快速排序和归并排序的复杂度都是O(nlogn),谁的速度更快?

快速排序的运行时间在1.39NlogN的某个常数因子的范围之内,归并排序也能做到这一点,但是快速排序一般会更快(尽管它的比较次数多39%),因为它移动数据的次数更少,这些保证都是来自于数学概率,完全可以相信它.

2.快速排序和堆排序的复杂度都是O(nlogn),而快速排序不稳定,有时会恶化到O(n^2),那为什么快速排序比堆排序更常用?

借鉴这里的分析:
因为堆排序下,数据读取的开销变大。在计算机进行运算的时候,数据不一定会从内存读取出来,而是从一种叫cache的存储单位读取。原因是cache相比内存,读取速度非常快,所以cache会把一部分我们经常读取的数据暂时储存起来,以便下一次读取的时候,可以不必跑到内存去读,而是直接在cache里面找。一般认为读取数据遵从两个原则:temporal locality,也就是不久前读取过的一个数据,在之后很可能还会被读取一遍;另一个叫spatial locality,也就是说读取一个数据,在它周围内存地址存储的数据也很有可能被读取到。因此,在读取一个单位的数据(比如1个word)之后,不光单个word会被存入cache,与之内存地址相邻的几个word,都会以一个block为单位存入cache中。另外,cache相比内存小得多,当cache满了之后,会将旧的数据剔除,将新的数据覆盖上去。在进行堆排序的过程中,由于我们要比较一个数组前一半和后一半的数字的大小,而当数组比较长的时候,这前一半和后一半的数据相隔比较远,这就导致了经常在cache里面找不到要读取的数据,需要从内存中读出来,而当cache满了之后,以前读取的数据又要被剔除。简而言之快排和堆排读取arr[i]这个元素的平均时间是不一样的。

3.快速排序算法为什么最常用?

快速排序算法是最快的通用排序算法,自从数十年前快速排序发明以来,它在无数计算机系统中无数实现已经证明了这一点.总的来说,快速排序之所以最快是因为它的内循环中的指令很少(而且它能利用缓存,因为它总是顺序地访问数据),所以它的运行时间的增长数量级为~cNlogN,而这里的c比其它线性对数级别的排序算法的相应常数都要小.
但需注意它只是在大多数实际情况中是最佳选择,当系统稳定性要求很高而空间又不是问题的时候,归并排序可能是最好的.

参考

本篇博客是本人看<<算法第四版>>排序部分之后的一个笔记,其中黄色部分的结论全部是从书上摘抄下来的,所以可以直接利用这些结论来进行算法的开发.

相关文章:

  • 2021-08-19
  • 2021-12-19
  • 2021-07-03
  • 2021-12-05
猜你喜欢
  • 2021-12-07
  • 2021-04-19
相关资源
相似解决方案