【算法】希尔排序

希尔排序
  • 算法描述

  • 希尔排序是简单插入排序的改进,它基于以下事实。

  • 简单插入排序对排序程度较高的序列有较高的效率。假设初始序列已完全排序,则每一轮均只需比较一次,将得到 O(n)O(n) 的线性复杂度,冒泡排序和选择排序做不到这一点,均仍需 O(n^2)O(n 2) 次比较(冒泡排序在应用提前结束优化后可以做到)。

  • 简单插入排序每次比较最多将数字移动一位,效率较低。

  • Donald Shell在1959年发表的论文中,针对第二点,提出如下方法。对原待排序列中相隔 gap 的数字执行简单插入排序,然后缩小 gap,对新的 gap 间隔的数字再次执行简单插入排序。以一种规则减少 gap 的大小,当 gap 为1时即简单插入排序,因此希尔排序也称作 增量递减排序。希尔在论文中提出的增量序列为{1, 2, 4, 8,…},即2^k,k = 1, 2, 3, …,在讨论希尔排序时,可将其称为 希尔增量。

  • 程序开始时 gap 较大,待排元素较少,因此排序速度较快。当 gap 较小时,基于第一点,此时待排序列已大致有序,排序效率接近线性复杂度。因此能够期待希尔排序复杂度将优于 O(n^2)。详细见「复杂度分析」。

  • 稳定性:不稳定。

  • gap > 1时,跨越gap的插入可能会改变两个相同值元素的位置关系。例如{0, 1, 4, 3, 3, 5, 6},当gap = 2时,对{0, 4, 3, 6}简单插入排序后得到{0, 1, 3, 3, 4, 5, 6},原数组中的两个3的位置互换了。

  • 复杂度分析

  • 时间复杂度:希尔排序的时间复杂度与增量序列的选择有关。最优复杂度增量序列尚未明确。

Shell增量(Shell, 1959): {1, 2, 4, 8,…},即 2^k2 k,k = 1, 2, 3, …,最坏时间复杂度 Θ(n^2)Θ(n 2)。

Hibbard增量(Hibbard, 1963):{1, 3, 7, 15,…},即 2^k - 12 k −1,k = 1, 2, 3, …,最坏时间复杂度 Θ(n^\frac{3}{2})Θ(n 23)。

Knuth增量(Knuth, 1971):{1, 4, 13, 40,…},即 (3^k - 1) / 2(3 k −1)/2,k = 1, 2, 3, …,最坏时间复杂度 Θ(n^\frac{3}{2})Θ(n 23 )。

Sedgewick增量(Sedgewick, 1982): {1, 8, 23, 77, 281},即 4^k + 3*2^(k-1) + 14 k +3∗2 ( k−1)+1 (最小增量1直接给出),k = 1, 2, 3, …,最坏时间复杂度 Θ(n^\frac{4}{3})Θ(n 34 )。

复杂度的证明需要借助数论和组合数学,略 (我不会)。

  • 空间复杂度:算法中只有常数项变量,O(1)O(1)。

  • 逆序数
    希尔排序是较早出现的 突破二次复杂度 的排序算法,下面从 逆序数 的角度来直观地证明为何希尔排序能够突破二次复杂度。

在一个排列中,如果任意一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个 逆序,一个排列中逆序的总数就称为这个排列的 逆序数。排序的过程就是不断减少逆序数 直到逆序数为 0 的过程。

回顾冒泡排序和简单插入排序,算法的每一次交换,都只交换相邻元素(简单插入排序中元素每次右移也看作交换),因此每次交换只能减少一个逆序。冒泡排序和简单插入排序的元素平均交换次数均为 O(n^2)O(n 2), 也即逆序数(或逆序数减少次数)为 O(n^2)O(n 2 )。 如果能跨越多个数字进行交换,则可能一次减少多个逆序。在选择排序中,每轮选到最小元素后的交换即是跨越多个元素的,交换次数(减少逆序数的操作)为 O(n)O(n),要少于冒泡和简单插入排序,只是因为比较次数仍是 O(n^2)O(n 2 ), 所以整体复杂度为 O(n^2)O(n 2 )。

现在来分析跨越多个元素的交换如何减少逆序数,假设 arr[i] > arr[j], i < j。对于任意的 arr[k] (i < k < j):

若 arr[k] < arr[j],交换 arr[i] 和 arr[j] 后,三者的逆序数从2变为1
若 arr[k] > arr[i],交换 arr[i] 和 arr[j] 后,三者的逆序数从2变为1
若 arr[i] > arr[k] > arr[j],交换 arr[i] 和 arr[j] 后,三者的逆序数从3变为0
arr[k] = arr[i] 或 arr[k] = arr[j] 的情况一样,都使得三者逆序数从2变为1,下图省略。
在这里插入图片描述
对 arr[i] 和 arr[j] 的逆序消除,使得逆序 至少 减少一次,并 有机会减少大于一次的逆序 (情况3),因此能够以比 n^2n 2 低阶的次数消除所有逆序。
实际上归并排序,快速排序,堆排序均实现了 长距离交换元素,使得复杂度优于 O(n^2)。
代码


// 希尔排序: 采用Knuth增量
public int[] shellSort(int[] arr) {
    if (arr.length < 2) return arr;
    int gap = 1; // 初始化gap
    while (gap < arr.length / 3) { // Knuth增量序列
        gap = gap * 3 + 1;
    }
    // 不断缩小gap直到1,对每一个gap值执行一次插入排序
    while (gap > 0) {
        for (int i = gap; i < arr.length; i += gap) { // 注意步长增量是gap
            int target = arr[i], j = i - gap;
            for (; j >= 0; j -= gap) { 
                 if (target < arr[j]) arr[j + gap] = arr[j];
                 else break; 
            }
            if (j != i - gap) arr[j + gap] = target; // 插入
        }
        gap /= 3; // 缩小gap值
    }
    return arr;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值