排序7:希尔排序

来源:互联网 发布:理财入门知乎 编辑:程序博客网 时间:2024/06/11 22:58

希尔排序是插入排序的改良版本,插入排序中前面是有序序列,每次将元素array[i]插入到前面有序序列中的合适位置,直接插入排序在插入时是逐个与前面的元素进行比较,即比较的步长为1,而希尔排序中,步长是一个逐渐变小的过程,对于数组6 5 3 1 8 7 2 4。例如第一次插入时步长为3,于是对于下标为012的元素不需要排序,从i=3即第4个元素开始进行插入,此时比较array[3]array[0]的大小,如果array[3]<array[0],那么就将array[3]array[0]进行交换,说明array[3]应该插入到前面去,然后i++在比较array[4]array[1]的大小,进行相同的交换……例如对于i=7的最后一个元素,它先与array[4]=8进行交换,此时array[4]=4,然后再与array[4-3=1]=5进行比较交换,于是array[1]=4,再往前就越界了。即对于某一个步长k,在遍历元素时总是与array[i-k]array[i-2*k]array[i-3*k]进行比较和交换,即在步长k的情况下,对于一个元素array[i],只需要考虑array[i-k]array[i-2*3]array[i-3*k]array[i-4*k]……(直到向前越界)

这个序列即可,即抽取这几个元素组成一个新的当前待排序的子序列数组。比较大小决定是否进行交换,注意,一个元素array[i]要与之前的所有元素进行比较和交换,直到再往前跳跃时越界,不能仅仅比较和交换1次。对于步长k=3遍历比较交换完成后对k进行调整,通常是k--;按照相同的过程进行遍历比较交换,此时从元素i=k=2进行向前的比较,前面的2个元素不用考虑顺序,比较array[i-2]array[i-2*2]array[i-3*2]……直到向前越界。不管步长的大小如何调整,最终步长k一定要调整为k=1,即对所有元素进行逐一比较交换,使得整个数组完全有序。当步长k=1时的排序就是一个直接插入排序,直接插入排序其实和任意步长k的插入排序思想都是一样的,就是逐个比较array[i-k]array[i-2*k]array[i-3*k],进行比较交换,只是当步长为k=1时的交换就是两个相邻元素之间的交换,前面插入排序中所将的将array[i]插入到前面有序序列中的j位置,其实就是通过对array[i-1],array[i-2],array[i-3]……逐一进行比较交换得到的,并不是找到位置后再将目标位置后面的元素统一向后移动一位,即还是基于比较交换的。

希尔排序进行了好几轮的插入排序,看似麻烦,但是当k值较大时,排序的粒度较粗,交换的元素较少,当k逐渐减小时,当前数组已经排序的程度逐渐提高,需要进行交换的次数变少,当最后k=1时只需要对很少的几个元素进行交换即可。根据统计规律可以得出结论,当步长k选择恰当时可以使得时间复杂度减少,最优时间复杂度为O(n),最劣时间复杂度为O(n^2),平均的时间复杂度为O(n^1.5),空间复杂度为O(1).

其实对于冒泡排序、插入排序、选择排序、归并排序、希尔排序、堆排序、快速排序,都是基于元素交换来实现的。

在写代码时,步长总是从int feet=length/2开始,逐步减小为一半(常识,除以2用>>来实现),直到feet>0不再满足,即最后一次遍历的步长总是1;对于每一个步长feet,从i=feet(注意对于步长为1时就从第2个元素即i=1,因为总是与前面的元素进行比较,开始遍历数组)开始遍历数组,对于每个i,比较array[i]与array[i-feet]、array[i-feet-feet](直到向前越界)进行比较。如果array[i]<前面某个元素就与它进行交换,直到找到在该步长数组中的合理的位置,即希尔排序是步长为feet的插入排序,插入的原理是不变的。希尔排序程序中有3层循环,最内层是对于元素array[i]遍历前面的元素找到合适的插入位置;中间层循环时对每个元素进行遍历和向前插入,外层循环时feet的遍历,由于feet是有限的,所以外层循环复杂度是常数C而不是n,对于内层的2层循环,最坏情形复杂度为O(n^2),即等于直接插入排序,但是一般复杂度为O(n^1.5)。在写代码时对于不同步长feet的遍历可以使用for循环、while循环也可以使用递归,显然这里使用的是尾递归,很容易的,就是while循环的递归形式而已。

import java.util.*;//希尔排序,对步长feet进行循环或者递归地缩短,直到收敛为步长为1的直接插入排序public class ShellSort{public int[] shellSort(int[] A, int n) {// 特殊输入if (A == null || A.length <= 0)return A;// 调用递归方法(尾递归)sort()来完成希尔排序        //注意习惯,题目中的数组通常以A给出,而自己喜欢用array表示数组,因此在调用函数时要记住传入的是Asort(A, A.length >> 1);// 记得要返回排序后的结果return A;}// 写一个排序方法sort(array,feet)用来使用步长feet对数组进行插入排序,内部调用的方法最好写成private方法private void sort(int[] array, int feet) {// 递归一定要有停止递归的边间条件if (feet <= 0)return;// 按照步长feet对数组array进行插入排序// 初始位置为i=feet;初始的比较位置是index=index-feetfor (int i = feet; i < array.length; i++) {// 要与前一个元素进行比较需要设置一个指针index,总是比较2个相邻的元素array[index]和array[index-feet],第一个元素是array[i]int index = i;// 如果index-feet<0说明index不要再往前交换了,本元素已经找到了合适的位置,停止循环while (index - feet >= 0) {if (array[index] < array[index - feet]) {// 如果后一个元素比前一个元素要小,应该交换元素this.swap(array, index, index - feet);index -= feet;} else {// 注意,还要有else,如果后面的元素大于等于前面的元素,不需要交换,说明元素array[index]之前已经找到合适的位置于是不需要再往前遍历了,结束本元素array[i]的插入,开始下一个i的向前插入break;}}}// 本步长的插入结束,此时需要更换步长feet,再次进行插入,于是递归调用sort()传入新的步长即可this.sort(array, feet >> 1);}// 写一个辅助函数用来交换2个元素private void swap(int[] array, int p1, int p2) {int temp = array[p1];array[p1] = array[p2];array[p2] = temp;}}


0 0
原创粉丝点击