几种常见的排序方法

最近两天看了一些排序的方法,顺带整理了下,便于理清思路:

由于数据存储的位置的原因,有的排序工作能在主存中完成,而有些不能,所以计算机中的排序(sorting)可以分为内部排序(internal sorting)和外部排序(external sorting),这里主要介绍几种内部排序方法。

 

内部排序:

1 插入排序(insertion sort),这是最简单的排序算法之一。对于p=1N-1,这种排序方法保证从位置0p的元素是已经排序了的。例如,一个数组:34864513221p=1后,它的就变成了83464513221,处理过程是:对于位置1上的元素(8),从位置0开始往前遍历,直到插入到正确的位置为止。

  由于每一次的循环嵌套都要花费N次迭代,因此插入排序为O(N²)。插入排序是比较常用的排序法之一,只用在小的或者是基本上排好序的输入数据上,因为在大的数据上它的效率就比较低。

 

2 谢尔排序(Shell Sort),又称“缩减增量排序”, 它使用一个叫做增量序列的一组序列h1h2h3…ht,只要能保证h1=1,那么任何的增量序列都是可行的。假设我们待排序的数组为a[],使用增量序列中的一个增量h的一趟排序后,对于每一个数组中的每个位置i,我们都有a[i]a[i+h],此时的数组称作h排序的。其实,每次的h排序,就是对于h个独立的子数组进行一次插入排序。

  比如有组待排序的数列: 81,94,11,96,12,35,17,95,28,58,41,75,15”,在5排序后,数列为“35,17,11,28,12,41,75,15,96,58,81,94,95”,然后在3排序后,数列又变为“28,12,11,35,15,41,58,17,94,75,81,96,95”,最后1排序后,就是最终的排序结果“11,12,15,17,28,35,41,58,75,81,94,95,96 ”。

 

3 堆排序(Heap Sort),优先队列可以以O(NlogN)的时间进行排序。我们将先建立N个元素的二叉堆,然后依次执行N次的“删除最小值”操作(将最小的元素移除二叉堆),同时每次把移除的元素记录到一个新的数组中。这样最后就能得到一个排好序的数组。因为每个“删除最小值”操作花费O(logN)的时间,所有堆排序总的排序时间为O(NlogN)

 (理解堆排序的前提是掌握优先队列(堆)的知识)

 

4 归并排序(Merge Sort),它的基本思想是使用递归的方法:归并的基本操作是合并两个已经排好序的数列,将结果输入到第三个表中。

  归并的方法是使用三个计数器分别记录每个数组的当前位置。假设现在有两个已经排好序的数组A(“1,13,24,26”),B(“2,15,27,38”),和一个存放合并结果的数组C,它们的计时器分别为abc,最初a指向“1”,b指向“2”,c指向C中的第一个位置0。第一次比较:将“1”放入C中,同时a指向后一个元素“13”,c指向C中的第二个位置1….在“26”被添加到C中后,数组A已经用完,所以将B中剩下的所有元素依次添加到C中。这样就完成了一次合并。

  合并两个已经排好序的数组的时间是线性的,因为最多进行了N-1次的比较。

  归并排序是经典的分治策略(divide and conquer),如果数组的个数为8,那么我们先将前4个和后4个数进行排序,然后再合并,而对于这两个子数组,又可以再进行这样的递归。

  所以,我们可以分析下归并排序的平均使用时间:当数列中的个数为0或者1时,用的时间就是常数时间,可以写作:T(1) = 1 根据每次合并的递推关系我们可以得出,T(N) = 2T(N)+ N。最后可以得出T(N) = N + N(logN)

 

5 快速排序(Quick Sort),它的平均运行时间也是O(NlogN)。快速排序也是一种使用分治思想的递归算法。它在待排序的数列中选取一个元素(一般是随机选取)v,称为枢纽元(pivot),根据v的大小,将数列其余的数分为小于v的一组,和大于v的一组,然后再分别以相同的方式处理这两个子数列。

 比如,现在有一个待排列的数列“1381433157650752692”,如果随机选取到枢纽元为“65”,则将得到小元素组“134331026”,大元素组“817592”,然后再以相同的方法排列小元素组和大元素组。

 上面说到了这个枢纽元是随机取的,但也是有讲究的。如果通过随机取一个数,这样的策略是安全的,但也要考虑到随机数的生成在时间上也是比较昂贵的。如果每次取数列中的第一个数,这样的方法是不可取的,因为如果这个数列已经是排了序的,那么得到小元素组或者大元素组将为空,这个操作时无用的。在实践中有一种方法,使用最左端,最后端和中间位置的三个数中的中数最为枢纽元。

  虽然快速排序的速度快,但是对于小数组,效率却不见的高,不建议使用。对于很小的数组(N20

 

6 间接排序(Indirect Sort),当需要排序的Comparable对象很大的情况,因为上面说的一些排序方法都有很多复制Comparable对象的操作,所以效率就会很低。

  间接排序的思想是生成一个指针数组,这些指针分别指向需要排序的Comparable对象,我们只要将这些指针重新排列就行了。

  C++,中vector不能完成这样的操作,所以我们需要使用一种叫“智能指针类”的指针,如果没有赋初始值,它可以自动地将自身初始化为NULL,同时还需要重载operator<</span>用来比较。

 

上面是我最近看的几个内部排序的方法,对我来说虽然能了解每个方法的意义,但是感觉仍然缺少项目中的实践来好好体验下这些排序算法的使用,希望有这样的一次机会。

 

外部排序:

内部排序的数据都需要把数据装入内存,并且可以直接寻址,但是由于磁带上的数据只能被顺序访问,所以内部排序的方法在这里就不适用了。

外部排序的一个简单算法: 假设有四盘磁带,ABCD,内存一次可以容纳3个记录,我们将处理过的排过序的一组记录称作一个顺串(run)。A中有待排序的数列“81,94,11,9612,35,17,99,28,58,41,75,15”,内存每一次处理完一个顺串后将它装入C,D中,所以,在这之后,C中有顺串“11,18,94”,“17,28,99”和“15”,D中有顺串“12,35,96”和“41,58,75”。然后我们将C,D中的第一个顺串取出来并且合并为一个顺串“11,12,35,81,94,96”并且放到A中,CD中的第二个顺串取出来合并为“17,28,41,58,75,99”放到B中,CD中的第三个顺串取出来合并为“15”放到A中。下面的步骤类似,则最后便能得到全部排序后的数列。

这里需要注意的是,合并两个排过序的表是简单的操作,几乎不需要内存,因为合并是在被合并的两个表前进是进行的。

我们发现,这个方法同样也用到了分治的思想。但我觉得这个思想的得出和其他的分治思想不太一样,它是一种“被动”的分治思想,因为内存处理不了所有需要排序的数,所以只能在最开始处理内存能容纳的那小部分数据。

外部排序的其他一些方法(多路合并,多相合并等)都是建立在这个简单算法的基础上进行改善的,所以在此就不写了。

 

我也是第一次集中地看了这些排序的算法,自己并没有很深入的研究了解过,只是很粗略的归纳了下,如有不妥之处,请不吝赐教!

原文地址:https://www.cnblogs.com/rambot/p/3660145.html