排序算法
(原创文章,转载请注明出处!)
一、插入排序
要点就是保持扫描过的元素是有序的,使之成为一个有序的元素序列(升序或降序)
每次取一个新的元素,扫描已排序的元素序列,找到其合适的位置,将新元素插入。
实例:打扑克时,揭牌保持扑克牌的顺序。
时间复杂度:O(N2)
算法的实现:使用能缓存一个元素的辅助存储空间。从0到N-1来扫描初始元素列表,从i到0扫描已经排序的元素,寻找i+1元素的合适位置。将第i+1元素存到辅助存储空间,将第i个元素移动到位置i+1,将第i-1个元素移动到位置i,以此类推,直到将第i+1个元素的合适位置空出来,然后将第i+1个元素放到合适的位置。
二、Shell排序
插入排序在序列基本有序时效率较高,并且在序列规模不是很大时效率也很高。Shell排序就是从这些方面对插入排序算法的改进。
shell排序先根据较长的步长对序列分组,进行排序,然后逐步减少步长再排序,直到步长为1为止。
初始时使用的步长大,每组的元素少,效率较高;
逐步的使用的步长小,每组的元素多了,但也基本有序了,效率依然较高。
例子: 12, 4,26,19,14,8,24,6,有8个元素。
第一轮:步长使用8/2=4,那么分成4组子序列进行排序,(1,5),(2,6),(3,7),(4,8)
序号1、5位置上分别是12和14, 12<14,不用动
序号2、6位置上分别是4和8, 4<8,不用动
序号3、7位置上分别是26和24, 26>24,交换
序号4、8位置上分别是19和6, 19>6,交换
序列变成:12 4 24 6 14 8 26 19
第二轮:步长使用4/2=2,那么分成2组子序列进行排序:(1,3,5,7),(2,4,6,8)
序号1,3,5,7位置上分别是12, 24 , 14 , 26 , 使用插入排序排完序,变成:12 , 14, 24, 26 (只有24与14进行一次交换)
序号2,4,6,8位置上分别是4, 6, 8, 19,(没有交换)
序列变成:12 4 14 6 24 8 26 19
第三轮:步长使用2/2=1,那么分成1组子序列进行排序,即整个序列:12 4 14 6 24 8 26 19
插入排序过程:4 12 14 6 24 8 26 19
4 6 12 14 24 8 26 19
4 6 8 12 14 24 26 19
4 6 8 12 14 19 24 26
结束。
三、快速排序
分治思想的应用。
时间复杂度:O(N(logN))
用所需要排序的序列中的某个元素p(比如:第一个)将序列一分为二:左边的元素都小于等于元素p,右边的都大于等于元素p;然后分别对左、右子序列进行相同划分;
最后将左、右子序列合并,则得到排好序的序列。
如何使用元素p来划分待排序序列?假设元素p是第一个元素(最左边的元素)。
策略一:元素P存到一个tmp,那么p的位置空出来了,记为vac;
从右(最右边的一个元素)往左,寻找比p小的元素,找到就放到vac位置,找到的比p小的元素的位置就空出来了,记为vac,新的最右边记为:H=vac-1;
从左边第二个元素开始往右,寻找比p大的元素,找到就放到vac位置,找到的比p大的元素的位置就空出来了,记为vac,新的最左边记为:L=vac+1;
再从H、L位置分别重复步骤二、三,直到H遇到L。
将元素p存到位置vac。
策略二:将元素p的位置记为M,从左(第二个元素)往右扫描序列,扫描到的位置记为I,
如果扫描到的元素比p小,就M=M+1,交换位置M与位置I的元素。
继续往右扫描,重复步骤二,直到全部扫描完。
四、混和排序
利用快速排序先进行大致的排序,比如:L > 15,15个元素内部无序的,但每15个、15个之间是有序的。
再利用插入排序来进行最后的,所有元素排序。
优点:插入排序在元素基本有序的情况下,效率很高。
五、堆排序
堆:一颗二叉树,具有如下的特点,
------父元素,比左右孩子都小。根元素是最小的元素。
------只有最后两层可能有叶子节点。节点在每一层中,从左往右排列。
堆的存储:使用数组。将元素按层,从左至右依次保存。元素Xi的父节点是Xi/2;元素Xi的左孩子X2i,右孩子X2i+1
堆的操作:
------删除最小的元素,即根元素:
- 将根元素与最后一个元素交换
- 然后再将新根元素往下移:
- 往下移的结束条件:元素处于叶子节点的位置,或者,元素小于或等于其左右子节点
- 选择较小的子节点,然后将元素与这个较小的子节点交换
- 重复迭代步骤2,直到把新根元素移到合适的位置。
删除根元素,也可以直接使用将最后一个元素往上移动的方式来调整堆。
------插入一个新元素:
- 将新元素加入到数组中的最后一个堆元素后面,
- 将新元素与其父元素比较,如果新元素小,则交换新元素与其父元素的位置。(这是将元素往上移动的操作)
- 重复迭代步骤2,直到把新元素交换到正确的位置
------将根元素用新元素替代
- 直接使用删除操作中的步骤2、3即可
利用堆进行排序:
扫描数组,将每个元素逐一插入到堆中,再逐一:输出堆的根元素,并删除它,堆调整。
注意要不使用另外一个数组作为辅助存储空间。堆的构建在原数组进行。
从i=1开始扫描数组,<i的元素已经是堆了,将元素i加入,调成<=i的数组中的元素,使之保持堆属性。
六、归并排序
归并排序是对分而治之思想的应用。将数组一分为二,分别对两个子数组排序,然后将两个排好序的子数组进行合并,得到最后排好序的数组。
对子数组的排序也是利用上述的分治的方法,所以归并排序是一个递归的过程。合并两个长度为n的子数组的时间复杂度是O(n),并且需要O(2n)的辅助空间来保存合并好的结果。算法的实现。还是使用上述的例子:
12, 4,26,19,14,8,24,6 。 8 个数组成的数列。
step1 : 分成两个子数组s1:12, 4,26,19 和 s2:14,8,24,6 ,分别对这两个子数组来进行归并排序
对s1: 分成两个子数组s11:12, 4 和 s12:26,19
对s11 使用归并排序,将12与4通过合并操作,得到排好序的数列 4 , 12
对s12 使用归并排序,将26与19通过合并操作,得到排好序的数列 19 , 26
对s11与s12使用合并操作,得到排好序的数列4 , 12, 19 , 26,则得到s1排好序的结果。
对s2进行相同的处理过程后,得到排好序的数列6, 8, 14, 24
然后,对s1与s2进行合并操作,得到整个数列最终的排序结果:4, 6, 8, 12, 14, 19, 24, 26 。
算法的实现有两个函数:mergeSort(), merge()
调用过程如下:
mergeSort( s1 )
(入栈)mergeSort(s1) |
mergeSort( s11 )
(入栈)mergeSort(s11) |
mergeSort(s1) |
mergeSort(12)
mergeSort(4)
merge(12, 4)
mergeSort(s11)(出栈) |
mergeSort(s1) |
mergeSort( s12 )
(入栈)mergeSort( s12 ) |
mergeSort( s1) |
mergeSort(26)
mergeSort(19)
merge(26, 19)
mergeSort( s12 ) (出栈) |
mergeSort( s1) |
merge(s11, s12)
mergeSort( s1) (出栈) |
mergeSort(s2)
(入栈)mergeSort( s2) |
........
merge(s1, s2) /* 得到最终排序的结果 */