希尔排序java实现

思想

shell排序是对直接插入排序的改进,我们已经知道,直接插入排序的复杂度受本身序列的有序程度影响,当数组已经有序,则每次查找插入的位置时间O(1),总的复杂度O(n),而当逆序对较多时,或者全部全部逆序时,复杂度为O(n^2)。所以希尔排序的思想是先优化子序列的有序程度,慢慢增大序列长度,最后对整个数组排序。

步骤

1、确定增量序列
2、按照增量序列分组,对每组进行直接插入排序(以第一个子序列的第二元素开始,向前面寻找更大的元素,没有则停下来)
3、重复1,直到增量为1

代码实现

class ShellSort{
    public static void main(String[] args) {
        int[] a={100,50,9,3, 4, 10, 3, 6, 7, 4, 1, 2};
        int n=a.length;
        int gap=n;
        while (gap>1){//最小间隔是1
            gap=gap/2;//最优增量缩减序列选择不确定,1/2是希尔建议的方式
            //其实下面的跟直接插入排序差不多,区别是开始索引是gap,查找间隔是gap
            for (int i=gap;i<n;i++){
                int k=a[i];//新元素
                int start=i-gap;//待比较元素
                while (start>=0&&a[start]>k){//大的往后移
                    a[start+gap]=a[start];
                    start-=gap;//间隔是gap
                }
                a[start+gap]=k;//把k放到合适的位置
            }
        }
        for (int i:a){
            System.out.print(i+" ");
        }
    }
}

算法分析

注意我们在每次对每个分组排序的时候不是分别对每个组分开排序,而是仍然从前往后扫描,那么显然,我们每次直接插入排序的key从每次的第一个子序列的第二个元素开始(与直接插入排序同理),然后挨个往后走,每个元素属于哪个组,我们就把它在其所在组进行直接插入排序。有点绕,仔细想想就好啦。
平均时间复杂度:o(N1.3),大量数据研究表明在o(N1.3)附近,事实上也不是严格论证的结果。
最好时间复杂度:仍然和增量序列的选取有关。
最坏时间复杂度:o(N^2)。
空间复杂度:o(1)只有常数个辅助变量。
稳定性:不稳定,例如待排序列(3,2,2,1),取d=2时,显然就能看出是不稳定的

希尔排序的性能在很大程度上依赖于所采用的**增量序列(gap sequence)**。不同的增量序列会显著影响排序的比较和移动次数,从而改变其实际运行效率。 --- ### 一、常见的增量序列 1. **Shell 原始序列(最原始)** - 增量公式:$ d_k = \left\lfloor \frac{n}{2^k} \right\rfloor $,每次除以 2 - 示例(n=8):8 → 4 → 2 → 1 - 特点:简单直观,但性能一般,最坏时间复杂度为 $ O(n^2) $ 2. **Knuth 序列** - 增量公式:$ d_k = \frac{3^k - 1}{2} $,从大到小取不超过 n 的值 - 示例:1, 4, 13, 40, 121, ... - 实际使用时逆序应用:如对长度为100的数组,用 40 → 13 → 4 → 1 - 时间复杂度约为 $ O(n^{1.5}) $ - 是较优的选择之一,被广泛推荐 3. **Hibbard 序列** - 增量公式:$ 2^k - 1 $,即 1, 3, 7, 15, 31, ... - 可证明最坏情况时间复杂度为 $ O(n^{3/2}) $ - 对某些数据分布表现良好 4. **Sedgewick 序列** - 定义复杂,形式为:1, 5, 9, 17, 29, 77, ... (有多种构造方式) - 例如:$ \{ \max(2^i \cdot 3^j) < n \} $ 或特定公式生成 - 经实验验证具有非常好的平均性能,时间复杂度接近 $ O(n^{4/3}) $ - 被认为是实践中最优的增量序列之一 5. **Ciura 序列(经验序列)** - 预定义的经验值:1, 4, 10, 23, 57, 132, 301, 701, ... - 不基于数学公式,而是通过大量实验优化得出 - 在实际应用中表现极佳,常用于现代实现中 --- ### 二、不同增量序列对性能的影响 | 增量序列 | 最坏时间复杂度 | 平均性能 | 说明 | |--------------|------------------------|-----------|------| | Shell 原始(÷2) | $ O(n^2) $ | 较差 | 当相邻元素分布在不同组时效率低,易退化 | | Knuth | $ O(n^{1.5}) $ | 中等偏上 | 结构合理,适合教学与一般用途 | | Hibbard | $ O(n^{3/2}) $ | 较好 | 理论保障较好,避免部分退化情况 | | Sedgewick | $ O(n^{4/3}) $ | 很好 | 实验性能优秀,适合高性能需求场景 | | Ciura(经验) | 未知(但非常快) | 极佳 | 当前公认最快的实用序列 | > ⚠️ 注意:所有增量序列都必须保证最后一个增量为 1,以确保最终进行一次完整的插入排序,使数组完全有序。 --- ### 三、代码示例(使用 Knuth 序列) ```python def shell_sort_knuth(arr): n = len(arr) # 生成 Knuth 增量序列:(3^k - 1)/2 < n gap = 1 while (3 * gap + 1) < n: gap = 3 * gap + 1 # 得到最大的小于 n 的 Knuth 数 while gap >= 1: for i in range(gap, n): temp = arr[i] j = i while j >= gap and arr[j - gap] > temp: arr[j] = arr[j - gap] j -= gap arr[j] = temp gap //= 3 # 回退到前一个 Knuth 数 return arr ``` --- ### 总结 - 增量序列决定了希尔排序的分组策略; - 更科学的序列(如 Ciura、Sedgewick)能大幅提高效率; - 教学中常用 Shell 原始序列或 Knuth 序列; - 实际工程中建议使用 Ciura 等经验序列以获得最佳性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值