插入排序之希尔排序

希尔排序是对直接插入排序的一个优化。

比如有这么一种情况:对一个无序数组进行从小到大的排序,但是数组的最后一个位置的数是最小的,我们要把它挪到第一个位置,其他位置的都要往后移动,要是这个数组非常大,那么直接插入排序的开销就非常大。------------希尔排序就可以解决这样的问题,也为其主要优点

希尔排序基本思想

算法先将要排序的一组数按某个增量d(n/2,n为要排序数的个数)分成若干组,每组中记录的下标相差d。对每组中全部元素进行直接插入排序,然后再用一个较小的增量(d/2)对它进行分组,在每组中再进行直接插入排序。当增量减到1时,进行直接插入排序后,排序完成。

希尔排序的关键并不是随便分组后各自排序,而是将相隔某个“增量”的记录组成一个子序列,实现跳跃式移动,使得排序的效率提高。需要注意的是,增量序列的最后一个增量值必须等于1才行。另外,由于记录是跳跃式的移动,希尔排序并不是一种稳定的排序算法。 

增量序列的选择
     Shell排序的执行时间依赖于增量序列。好的增量序列的共同特征:
  ① 最后一个增量必须为1;
  ② 应该尽量避免序列中的值(尤其是相邻的值)互为倍数的情况。
     有人通过大量的实验,给出了目前较好的结果:当n较大时,比较和移动的次数约在nl.25到1.6n1.25之间。

Shell排序的时间性能优于直接插入排序
     希尔排序的时间性能优于直接插入排序的原因:
  ①当文件初态基本有序时直接插入排序所需的比较和移动次数均较少。
  ②当n值较小时,n和n2的差别也较小,即直接插入排序的最好时间复杂度O(n)和最坏时间复杂度0(n2)差别不大。
  ③在希尔排序开始时增量较大,分组较多,每组的记录数目少,故各组内直接插入较快,后来增量di逐渐缩小,分组数逐渐减少,而各组的记录数目逐渐增多,但由于已经按di-1作为距离排过序,使文件较接近于有序状态,所以新的一趟排序过程也较快。

稳定性: 希尔排序是不稳定的。

 

排序过程如下:

以数组{26, 53, 67, 48, 57, 13, 48, 32, 60, 50 }为例,步长序列为{5,2,1} 
初始化关键字: [26, 53, 67, 48, 57, 13, 48, 32, 60, 50 ]

这里写图片描述

最后的排序结果: 
13 26 32 48 48 50 53 57 60 67

 

代码实现:

第一种:主要关注increment如何取值 Math.ceil(increment/2)

    public static void shellSort(int[] array){
        double increment = array.length;//增量长度
        int h,sentinel,k;
        while (true) {
            increment = (int)Math.ceil(increment/2);//逐渐减小增量长度
            h = (int)increment;//确定增量长度
            for (int i = 0; i < h; i++) {
                //用增量将序列分割,分别进行直接插入排序。随着增量变小为1,最后整体进行直接插入排序
                for (int j = i+h; j < array.length; j+=h) {
                    k = j - h;
                    sentinel = array[j];//哨兵位
                    while (k >= 0 && sentinel < array[k]) {
                        array[k+h] = array[k];//k+dk位始终虚位以待
                        k = k - h;
                    }
                    array[k+h] = sentinel;
                }
            }
            //当dk为1的时候,整体进行直接插入排序
            if (dk == 1) {
                break;
            }
        }
    }

第二种:对比第一种increment如何取值 ,increment = data.length / 2

public static void shellSortSmallToBig(int[] data) {
        int j = 0;
        int temp = 0;
        for (int increment = data.length / 2; increment > 0; increment /= 2) {
            System.out.println("increment:" + increment);
            for (int i = increment; i < data.length; i++) {
                // System.out.println("i:" + i);
                temp = data[i];
                for (j = i - increment; j >= 0; j -= increment) {

                    if (temp < data[j]) {
                        data[j + increment] = data[j];
                    } else {
                        break;
                    }
                }
                data[j + increment] = temp;
            }
            for (int i = 0; i < data.length; i++)
                System.out.print(data[i] + " ");
        }
    }

    public static void main(String[] args) {
        int[] data = new int[] { 26, 53, 67, 48, 57, 13, 48, 32, 60, 50 };
        shellSortSmallToBig(data);
        System.out.println(Arrays.toString(data));
    }

第三种:increment?

public static void shellSort(int[] array){
        int h = 1;//增量长度,计算最大的h
        while (h <= array.length/3) {
           h = h * 3 + 1;
    }
        while (h > 0) { //进行间隔为h的插入排序
            for (int i = h; i < array.length; i++) {
                if(array[i] < array[i - h]){                  
                    int k = i - h;
                    sentinel = array[i];
                    while (k >= 0 && sentinel < array[k]) {
                        array[k+h] = array[k];
                        k = k - h;
                    }
                    array[k+h] = sentinel;
                }
            }
      h = (h -1) / 3 ; //计算出下一个h值,每次排序结束后,递减h的值 } }

 

疑问1:同样的一个待排序数组,increment取值方式略有不同,这使用哪种效率最高?

疑问2:能否改成并行的?

答:可以。因为,每次都针对不同的子数组进行排序,各个子数组之间是完全独立的。很容易实现并行程序。

 

并行实现:

代码中定义shellSortTask作为并行任务。一个shellSortTask的作用是根据给定的起始位置和h,对子数组进行排序,因为可完全并行化。

为控制线程数量,定义并行主函数pShellSort()在h大于或等于4时使用并行线程(第40行),否则退化为传统的插入排序。每次计算后,递减h的值。

 

参考资料:

1、希尔排序算法原理及JAVA实现

2、【排序算法】希尔排序原理及Java实现

3、《java 高并发程序设计》

posted @ 2017-04-10 15:32  milkty  阅读(312)  评论(0)    收藏  举报