排序算法之希尔排序

插入排序详解


一、前言

1959 年 77 月,美国辛辛那提大学的数学系博士 Donald Shell 在 《ACM 通讯》上发表了希尔排序算法,成为首批将时间复杂度降到 O(n^2)
以下的算法之一。虽然原始的希尔排序最坏时间复杂度仍然是 O(n^2)
但经过优化的希尔排序可以达到 O(n^{1.3})
甚至 O(n^{7/6})。

略为遗憾的是,所谓「一将功成万骨枯」,希尔排序和冒泡、选择、插入等排序算法一样,逐渐被快速排序所淘汰,但作为承上启下的算法,不可否认的是,希尔排序身上始终闪耀着算法之美。

希尔排序本质上是对插入排序的一种优化,它利用了插入排序的简单,又克服了插入排序每次只交换相邻两个元素的缺点。它的基本思想是:

  • 将待排序数组按照一定的间隔分为多个子数组,每组分别进行插入排序。这里按照间隔分组指的不是取连续的一段数组,而是每跳跃一定间隔取一个值组成一组
  • 逐渐缩小间隔进行下一轮排序
  • 最后一轮时,取间隔为 11,也就相当于直接使用插入排序。但这时经过前面的「宏观调控」,数组已经基本有序了,所以此时的插入排序只需进行少量交换便可完成
  • 举个例子,对数组 [84, 83, 88, 87, 61, 50, 70, 60, 80, 99][84,83,88,87,61,50,70,60,80,99] 进行希尔排序的过程如下:
  • 第一遍(55 间隔排序):按照间隔 55 分割子数组,共分成五组,分别是 [84, 50], [83, 70], [88, 60], [87, 80], [61, 99][84,50],[83,70],[88,60],[87,80],[61,99]。对它们进行插入排序,排序后它们分别变成: [50, 84], [70, 83], [60, 88], [80, 87], [61, 99][50,84],[70,83],[60,88],[80,87],[61,99],此时整个数组变成 [50, 70, 60, 80, 61, 84, 83, 88, 87, 99][50,70,60,80,61,84,83,88,87,99]
  • 第二遍(22 间隔排序):按照间隔 22 分割子数组,共分成两组,分别是 [50, 60, 61, 83, 87], [70, 80, 84, 88, 99][50,60,61,83,87],[70,80,84,88,99]。对他们进行插入排序,排序后它们分别变成: [50, 60, 61, 83, 87], [70, 80, 84, 88, 99][50,60,61,83,87],[70,80,84,88,99],此时整个数组变成 [50, 70, 60, 80, 61, 84, 83, 88, 87, 99][50,70,60,80,61,84,83,88,87,99]。这里有一个非常重要的性质:当我们完成 22 间隔排序后,这个数组仍然是保持 55 间隔有序的。也就是说,更小间隔的排序没有把上一步的结果变坏。
  • 第三遍(11 间隔排序,等于直接插入排序):按照间隔 11 分割子数组,分成一组,也就是整个数组。对其进行插入排序,经过前两遍排序,数组已经基本有序了,所以这一步只需经过少量交换即可完成排序。排序后数组变成 [50, 60, 61, 70, 80, 83, 84, 87, 88, 99][50,60,61,70,80,83,84,87,88,99],整个排序完成。

动图演示:
在这里插入图片描述


二、交换法希尔排序

交换法希尔排序其实就是在用插入排序的时候采用交换法插入排序。

    /**
     * 交换法希尔排序
     *
     * @param arr
     */
    public static void shellSort1(int[] arr){
        for (int group = arr.length / 2 ;  group > 0 ; group /= 2){

            for (int i = group ; i < arr.length ; i ++){
                //当前要插入的元素的下标
                int j = i;
                while (j - group >= 0  && arr[j] < arr[j - group]){
                    swap(arr,i,i-group);
                    i -= group;
                }
            }
        }
    }
private static void swap(int[] arr, int i, int j){

        int temp = arr[i];

        arr[i] = arr[j];

        arr[j] = temp;
    }

客户端:

public static void main(String[] args){

        int[] arr = {2,3,5,1,2,4,5,6,8,6,7,9};
        shellSort2(arr);

        for (int i : arr) {
            System.out.print(i+"-");
        }
    }

输出结果为:

1-2-2-3-4-5-5-6-6-7-8-9-

三、移动法希尔排序

同理,移动法希尔排序其实就是在用插入排序的时候采用移动法插入排序。

/**
     * 移动法希尔排序
     *
     * @param arr
     */
    public static void shellSort2(int[] arr){

        for (int group = arr.length / 2 ;  group > 0 ; group /= 2){

            for (int i = group ; i < arr.length ; i ++){

                //用于记录插入元素前一个元素的下标
                int j = i - group;

                //记录当前正在插入的元素
                int current = arr[i];

                while (j >= 0 && current < arr[j] ){

                    arr[j + group] = arr[j];

                    j -= group;
                }

                arr[j + group] = current;


            }
        }
    }

客户端:

public static void main(String[] args){

        int[] arr = {2,3,5,1,2,4,5,6,8,6,7,9};
        shellSort2(arr);

        for (int i : arr) {
            System.out.print(i+"-");
        }
    }

输出结果为:

1-2-2-3-4-5-5-6-6-7-8-9-

四、时间复杂度和空间复杂度

事实上,希尔排序时间复杂度非常难以分析,它的平均复杂度界于 O(n)到 O(n^2)
之间,普遍认为它最好的时间复杂度为 O(n^{1.3})


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值