希尔排序
希尔排序是一种基于插入排序(参见http://blog.csdn.net/a19881029/article/details/21624081)的排序算法
插入排序的问题是,如果一个极小的数据处于数组的右端,那么在数组排序过程中,每个数据项平均需要移动N/2次,所有数据大约需要移动N*N/2次,时间复杂度为O(N*N),也就是说在排序过程中,数据项需要移动的次数过多,严重降低了排序的效率
如何降低排序过程中数据项的移动次数呢?如果原始数组是一个基本有序的数据,那么平均每个数据项只需移动1次,或不需要移动,所有数据大约只需移动N次,时间复杂度将变为O(N)
所以提高插入排序效率的关键就在于如何将原始数组变为一个基本有序的数组,这就需要在原有插入算法的基础上做一点改进
希尔排序通过加大插入排序中数据项之间的间隔,并且对这些有间隔的数据项进行插入排序,使数据项可以大跨度的移动,当数组排过一次后,缩小数据项之间的间隔再次进行排序,依次进行下去。最后一次排序数据项之间的间隔为1,是一次普通的插入排序
一个长度为10的数组升序排序,首先进行间隔为4的插入排序
此时数组已经基本有序(数组项离其最终位置不超过2个单元),对最后的结果再进行普通的插入排序,这时大约只需要O(N)的时间
当间隔比较大时,每次排序需要移动的数据项较少,但是移动距离较大,这是非常有效率的,当间隔变小时,虽然每次排序需要移动的数据项很多,但此时数组已经基本有序,这对插入排序来说也是非常有效率的
排序间隔:
一般通过公式h=h*3+1得到间隔序列,在进行希尔排序时,使用该序列的逆向序列作为间隔,当然初始间隔不能超过数组的长度
当然也可以使用其它的间隔序列,只是要保证逐渐减小的间隔最后一定要等于1,这样可以保证希尔排序过程中最后一次排序是一次普通的插入排序
使用某些间隔序列会使运行时间降低为O(N*N),为了避免这种情况,需要保证间隔序列中的各个数字之间互质,这样可以使每一次排序更有可能保持前一次排序已经排好的效果
当然间隔序列必须可以被简单高效的计算出来,避免影响算法的执行效率
public class ShellSort { private int[] data; public ShellSort(int[] data){ this.data = data; } public void sort(){ int num = data.length; int h = 1; while(h*3 + 1 < num) h = h*3 + 1; while(h > 0){ System.out.println(h); for(int i = h ; i < data.length ; i++){ int tmp = data[i]; int insertPoint = i; while(insertPoint - h >= 0 && data[insertPoint-h] > tmp){ data[insertPoint] = data[insertPoint-h]; insertPoint -= h; } data[insertPoint] = tmp; } h = (h - 1)/3; } } public void display(){ for(int i = 0 ; i < data.length ; i++){ System.out.print(data[i] + " "); } System.out.print("\n"); } public static void main(String[] args) { int[] data = {7, 10, 1, 9, 2, 5, 8, 6, 4, 3}; ShellSort ss = new ShellSort(data); ss.sort(); ss.display(); } }
4 1 1 2 3 4 5 6 7 8 9 10
希尔排序的效率:
除了某些特殊的情况,无法在理论上分析希尔排序的效率
有很多基于实验的评估,希尔排序的效率大约在O(N3/2)和O(N7/6)之间,比时间复杂度为O(N*N)的排序方式要快很多
希尔排序在最坏情况下的执行效率和平均情况下的执行效率相比没有差太多,不像快速排序在最坏条件下,执行效率会非常差,并且不像表排序和归并排序那样需要额外的存储空间