常用排序算法

来源:互联网 发布:跑腿app源码 编辑:程序博客网 时间:2024/04/28 14:48

排序算法:

内部排序算法大致分为两种:

一种是比较排序,时间复杂度最少可达到O(n log n),主要有:冒泡排序,选择排序,插入排序,归并排序,堆排序,快速排序等。

另一种是非比较排序,时间复杂度可以达到O(n),主要有:计数排序,基数排序,桶排序等。


稳定性:

假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,ri=rj,且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的

对于不稳定的排序算法,只要举出一个实例,即可说明它的不稳定性;而对于稳定的排序算法,必须对算法进行分析从而得到稳定的特性。需要注意的是,排序算法是否为稳定的是由具体算法决定的,不稳定的算法在某种条件下可以变为稳定的算法,而稳定的算法在某种条件下也可以变为不稳定的算法。


时间复杂度与空间复杂度:



冒泡排序(Bubble Sort):

遍历序列,挑去最大值/最小值,一步步位移比较,放置在最右侧/最左侧。

步骤:

1.比较相邻的元素,如果前一个比后一个大,就把它们两个调换位置。
2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
3.针对所有的元素重复以上的步骤,除了最后一个。
4.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。



冒泡排序的改进:鸡尾酒排序 Cocktail sort

又称定向冒泡排序, 鸡尾酒搅拌排序, 搅拌排序 (也可以视作选择排序的一种变形), 涟漪排序, 来回排序 or 快乐小时排序, 是冒泡排序的一种变形。此演算法与冒泡排序的不同处在于排序时是以双向在序列中进行排序。

与冒泡排序不同的是,先找最大值/最小值,再找最小值/最大值,交叉的找。其实时间复杂度没有任何变化,只是在一定特殊序列中能起到很好的作用。例如:(2,3,4,5,1)



选择排序(Selection Sort):

选择排序也是选出最大值/最小值,放在最右侧/最左侧,但是与冒泡排序不同的是,选择排序在寻找最大值/最小值时不会进行位移操作,仅仅是记录,知道寻找完成后再与最右侧/最左侧的值进行互换操作。

上述代码对序列{ 8, 5, 2, 6, 9, 3, 1, 4, 0, 7 }进行选择排序的实现过程如右图:

选择排序是不稳定的排序算法,不稳定发生在最小元素与A[i]交换的时刻。
比如序列:{ 5, 8, 5, 2, 9 },一次选择的最小元素是2,然后把2和第一个5进行交换,从而改变了两个元素5的相对次序。


插入排序(Insertion Sort):

类似于抓扑克,抓一张插入一张。

对于未排序数据(抓到的牌),在已排序序列(已经排好序的手牌)中从后向前扫描,找到相应位置并插入。
插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

步骤:

1.从第一个元素开始,该元素可以认为已经被排序
2.取出下一个元素,在已经排序的元素序列中从后向前扫描
3.如果该元素(已排序)大于新元素,将该元素移到下一位置
4.重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
5.将新元素插入到该位置后
6.重复步骤2~5


插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,比如量级小于千,那么插入排序还是一个不错的选择。 插入排序在工业级库中也有着广泛的应用,在STL的sort算法和stdlib的qsort算法中,都将插入排序作为快速排序的补充,用于少量元素的排序(通常为8个或以下)。


插入排序的改进:二分插入排序 Binary search

又称折半插入排序,对于插入排序,如果比较操作的代价比交换操作大的话,可以采用二分查找法来减少比较操作的数目,我们称为二分插入排序。

就是查找步骤不是一个个差,而是变成了使用二分法查找,时间复杂度依旧为O(n^2),但是理论上要快一点、、、


插入排序的更高效改进:希尔排序(Shell Sort)

希尔排序,也叫递减增量排序,是插入排序的一种更高效的改进版本。希尔排序是不稳定的排序算法。

该方法的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率上比前两种方法有较大提高。

步骤:

以n=10的一个数组49, 38, 65, 97, 26, 13, 27, 49, 55, 4为例

1.第一次 gap = 10 / 2 = 5,将1,2,3,4,5组分别排序,组内互换位置。


2.第二次 gap = 5 / 2 = 2:

3.第三次 gap = 2 / 2 = 1:

4.第四次 gap = 1 / 2 = 0 排序完成得到数组:

宏观视图:

希尔排序是不稳定的排序算法,虽然一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱。


归并排序(Merge Sort):

归并排序的实现分为递归实现与非递归(迭代)实现。递归实现的归并排序是算法设计中分治策略的典型应用,我们将一个大问题分割成小问题分别解决,然后用所有小问题的答案来解决整个大问题。非递归(迭代)实现的归并排序首先进行是两两归并,然后四四归并,然后是八八归并,一直下去直到归并了整个数组。

步骤:

1.申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
2.设定两个指针,最初位置分别为两个已经排序序列的起始位置
3.比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
4.重复步骤3直到某一指针到达序列尾
5.将另一序列剩下的所有元素直接复制到合并序列尾

对序列{ 6, 5, 3, 1, 8, 7, 2, 4 }进行归并排序:

宏观视图:


堆排序(Heap Sort):

堆排序是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构(通常堆是通过一维数组来实现的),并同时满足堆的性质:即子结点的键值总是小于(或者大于)它的父节点。

二叉堆的定义:

二叉堆是完全二叉树或者是近似完全二叉树。
二叉堆满足二个特性:
1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。
当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆。当父结点的键值总是小于或等于任何一个子节点的键值时为最小堆。下图展示一个最小堆:

步骤:

1.创建一个堆
2.把堆顶元素(最大值)和堆尾元素互换
3.把堆的尺寸缩小1,并调用heapify(A, 0)从新的堆顶元素开始进行堆调整
4.重复步骤2,直到堆的尺寸为1

堆排序是不稳定的排序算法,不稳定发生在堆顶元素与A[i]交换的时刻。

比如序列:{ 9, 5, 7, 5 },堆顶元素是9,堆排序下一步将9和第二个5进行交换,得到序列 { 5, 5, 7, 9 },再进行堆调整得到{ 7, 5, 5, 9 },重复之前的操作最后得到{ 5, 5, 7, 9 }从而改变了两个5的相对次序。


快速排序(Quicksort):

在平均状况下,排序n个元素要O(nlogn)次比较。在最坏状况下则需要O(n^2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他O(nlogn)算法更快,因为它的内部循环可以在大部分的架构上很有效率地被实现出来。

步骤:

1.从序列中挑出一个元素,作为"基准"(pivot),
2.重新排序序列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准的后面(相同的数可以到任一边)。这个称为分区(partition)操作。
3.递归地把小于基准值元素的子数列和大于基准值元素的子数列排序。递归的最底部情形,是序列的大小是0或1,也就是永远都已经被排序好了。


快速排序是不稳定的排序算法,不稳定发生在基准元素与A[tail+1]交换的时刻。

比如序列:{ 1, 3, 4, 2, 8, 9, 8, 7, 5 },基准元素是5,一次划分操作后5要和第一个8进行交换,从而改变了两个元素8的相对次序。


基数排序(Radix Sort):

将所有待比较正整数统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。个十百依次排序。

基数排序的时间复杂度是O(n * dn),其中n是排序元素个数,dn是数字位数。注意这不是说这个时间复杂度一定优于O(n log n),dn的大小取决于数字位的选择(比如比特位数),和待排序数据所属数据类型的全集的大小;dn决定了进行多少轮处理,而n是每轮处理的操作数目。


桶排序(Bucket sort):

又称箱排序,将数组分到有限数量的桶子里,每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。

步骤:

1.设置一个定量的数组当作空桶子。
2.寻访序列,并且把项目一个一个放到对应的桶子去。
3.对每个不是空的桶子进行排序。
4.从不是空的桶子里把项目再放回原来的序列中。


桶排序并不是比较排序,它不受到O(nlogn)下限的影响。它是鸽巢排序的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间


鸽巢排序(Pigeonhole Sort):

感觉是最丧心病狂的排序方式,时间复杂度只有O(n)就能解决,但是空间复杂度爆炸,原理如下:

1.找到序列中最大值Max

2.重新创建另一个长度为Max的数组Max[]

3.遍历原数组,使用Max[]数组记录原数组各个数的频率

4.遍历Max[]数组,依次输出,排序完成。


计数排序(Counting Sort):

鸽巢排序其实就是计数排序的特定情形,鸽巢排序的计的是 0 到 max 的,计数排序计的是 min 到 max。


总结:


0 0