希尔排序原理图解

一/😋案例步骤分解
二/😁Java代码实现—— 锻炼逻辑思维,代码不是必要 还是挺重要的
三/🧐还有一个问题—GAP怎么界定?

四/😎时间复杂度、空间复杂度及稳定性

希尔排序是D.L.Shell于1959年提出的因而得名。希尔排序就是插入排序的一种优化升级版,同时它是第一批在时间复杂度上突破O(n^2)的排序算法,所以意义深远。希尔排序会事先规定一个间隔值 gap 。假设有一个长度为n的数组arr,希尔排序会先对arr的子数组{arr[0],arr[gap],arr[gap2],…}进行简单插入排序,再对子数组{arr[1],arr[1+gap],arr[1+gap2],…}进行简单插入排序,这样依次进行直到子数组
{arr[gap-1],arr[gap2-1],arr[gap3-1],…}

  • 我们先来回顾一下插入排序(图片来源于网络)插入排序的算法虽然不如选择排序和冒泡排序简单粗暴,但其原理打过牌的人都能理解——斗地主时抓到一张牌你会按大小顺序插入手中的牌,比如你手里现在有黑桃3,红桃4,梅花6和方块K并已经按顺序排好,这时候你抓到了一张梅花5,你会把它放到红桃4和梅花6的中间。✅相比计算机人脑是一个更完善的机器,再加上多年的练习,这个过程在你的大脑中是一瞬间完成的以至于你可能都未曾意识到。☄🌠无形当中其实你已经在大脑中对其进行了一遍插入排序➡5和K比,比K小➡再和6比,比6小➡继续和4比,比4大➡至此结束,5就插到6和4中间。
    图片来源于网络
  • 那为什么说希尔排序是针对插入排序的一种优化呢? 我们都知道,插入排序在两种情况下工作量最小
  1. 在大多数元素基本有序的情况下。
    元素基本有序的话,元素之间也就不需要频繁地交换,工作量自然减少
  2. 在元素数量比较少的情况下。
    元素数量n变小,时间复杂度O(n^2)自然也变小。
    有人👀说:这怎么可能?(图片来源:https://baijiahao.baidu.com/s?id=1644158198885715432&wfr=spider&for=pc)
    在这里插入图片描述
    (文中小老鼠图片来源:https://baijiahao.baidu.com/s?id=1644158198885715432&wfr=spider&for=pc)
    当然可能🧔希尔排序就是基于这样的思想。先规定一个间隔,按间隔组成多个子数组,分别对子数组进行直接插入排序——这时候每个字数组符合元素数量比较少的情况;然后渐渐缩小间隔值,直到间隔为1是就是直接插入排序——这时候数组已经基本有序。

🌝接下来通过一个案例来说明吧。

一/😋案例步骤分解

假设定义一个数组int[] arr={9,8,7,6,5,4,3,2,1}如下,长度为9,这里先规定gap=4,其希尔排序流程如下 (颜色相同的是一组)
在这里插入图片描述

  1. 先对子数组{9,5,1}进行插入排序,其他位置元素不变,结果如下
    187654329
  2. 再对{8,4}插入排序,其他位置不变,结果如下

147658329

  1. 依次再对{7,3}{6,2}{5,9}重复以上动作,这里不一一演示了,全部完成后结果如下:

143258769

  1. gap=4的情况就全部排完了,这时候令gap缩小为2,重复以上动作;
    143258769排完序是这样
    123456789

  2. 这时候其实已经排完了,再令gap=1(就是简单插入排序了)走一遍流程,最后得出排好序的数组

123456789

二/😁Java代码实现—— 锻炼逻辑思维,代码不是必要 还是挺重要的

public class ShellSortPra {
    public static void main(String[] args) {
        int[] arr = {9, 8, 7, 6, 5, 4, 3, 2, 1};
        sort(arr);
        print(arr);
    }

    //希尔排序算法的排序主方法
    static void sort(int[] arr) {
        int gap;
        for (gap = 4; gap > 0; gap /= 2) {//规定间隔为4,之后每次减半
            for (int j = gap; j < arr.length; j++) {
                for (int i = j; i > gap - 1; i -= gap) {
                    if (arr[i] < arr[i - gap]) {
                        swap(arr, i, i - gap);
                        System.out.println("gap=" + gap + " j=" + j + " i=" + i);
                        print(arr);
                        System.out.println("\n");
                    } else {
                        System.out.println("gap=" + gap + " j=" + j + " i=" + i);
                        print(arr);
                        System.out.println("\n");
                    }
                }
            }
        }
    }

    //打印数组的方法封装
    static void print(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
    }

    //交换元素的方法封装
    static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

🐱‍💻这段代码是怎么思考出来的?

由于是直接插入排序的改造,所以我们基于直接插入排序的代码,来贴上

static void sort(int[] arr) {
    for (int j = 1; j < arr.length; j++) {
        for (int i = j; i > 0; i--) {
            if (arr[i] < arr[i - 1]) {
                swap(arr, i, i - 1);
            }
        }
    }
}
  1. 第一步就是先定义gap=4;
  2. 修改外层循环。由于直接插入排序就是gap=1,可以推出第二步外层循环j的初始值应该赋值为gap,后面的 j < arr.length; j++不变。j++(每次自增1)为什么不变?j 是内循环 i 的初始值(也是本次计算子数组的末位值的索引),内循环一次可以看成是子数组的一次直接插入排序;而 j 自增一次就规定了下一个计算的子数组的末位值,因为各个子数组的末位值是一一紧靠排列的,因此 j 自然也是每次自增1;
  3. 修改内层循环。内循环的条件即是构成了间隔为gap的子数组,所以 i>0应该改为 i > gap-1; i-- (每次自减1)改成 i -= gap(每次自减gap);同时if语句的条件中的索引 i - 1 也要改成 i - gap,同时修改swap方法中的相应变量值。
  4. 最后加上gap值的循环,这里设定为每次自除以2。
    🧑修改后的sort方法如下:
static void sort(int[] arr) {
    int gap;
    for (gap = 4; gap > 0; gap /= 2) {
        for (int j = gap; j < arr.length; j++) {
            for (int i = j; i > gap - 1; i -= gap) {
                if (arr[i] < arr[i - 1]) {
                    swap(arr, i, i - gap);
                }
            }
        }
    }
}

*大家可以看到我在循环里加入了一段小代码

System.out.println("gap=" + gap + " j=" + j + " i=" + i);
print(arr);
System.out.println("\n");

来体现这个程序的计算步骤,也就是第一步的“步骤分解”,这样更便于直观地理解算法的运行流程,大家可以自己尝试一下,运行结果:

gap=4 j=4 i=4
5 8 7 6 9 4 3 2 1 

gap=4 j=5 i=5
5 4 7 6 9 8 3 2 1 

gap=4 j=6 i=6
5 4 3 6 9 8 7 2 1 

gap=4 j=7 i=7
5 4 3 2 9 8 7 6 1 

gap=4 j=8 i=8
5 4 3 2 1 8 7 6 9 

gap=4 j=8 i=4
1 4 3 2 5 8 7 6 9 

gap=2 j=2 i=2
1 4 3 2 5 8 7 6 9 

gap=2 j=3 i=3
1 2 3 4 5 8 7 6 9 

gap=2 j=4 i=4
1 2 3 4 5 8 7 6 9 

gap=2 j=4 i=2
1 2 3 4 5 8 7 6 9 

gap=2 j=5 i=5
1 2 3 4 5 8 7 6 9 

gap=2 j=5 i=3
1 2 3 4 5 8 7 6 9 

gap=2 j=6 i=6
1 2 3 4 5 8 7 6 9 

gap=2 j=6 i=4
1 2 3 4 5 8 7 6 9 

gap=2 j=6 i=2
1 2 3 4 5 8 7 6 9 

gap=2 j=7 i=7
1 2 3 4 5 6 7 8 9 

gap=2 j=7 i=5
1 2 3 4 5 6 7 8 9 

gap=2 j=7 i=3
1 2 3 4 5 6 7 8 9 

gap=2 j=8 i=8
1 2 3 4 5 6 7 8 9 

gap=2 j=8 i=6
1 2 3 4 5 6 7 8 9 

gap=2 j=8 i=4
1 2 3 4 5 6 7 8 9 

gap=2 j=8 i=2
1 2 3 4 5 6 7 8 9 

gap=1 j=1 i=1
1 2 3 4 5 6 7 8 9 

gap=1 j=2 i=2
1 2 3 4 5 6 7 8 9 

gap=1 j=2 i=1
1 2 3 4 5 6 7 8 9 

gap=1 j=3 i=3
1 2 3 4 5 6 7 8 9 

gap=1 j=3 i=2
1 2 3 4 5 6 7 8 9 

gap=1 j=3 i=1
1 2 3 4 5 6 7 8 9 

gap=1 j=4 i=4
1 2 3 4 5 6 7 8 9 

gap=1 j=4 i=3
1 2 3 4 5 6 7 8 9 

gap=1 j=4 i=2
1 2 3 4 5 6 7 8 9 

gap=1 j=4 i=1
1 2 3 4 5 6 7 8 9 

gap=1 j=5 i=5
1 2 3 4 5 6 7 8 9 

gap=1 j=5 i=4
1 2 3 4 5 6 7 8 9 

gap=1 j=5 i=3
1 2 3 4 5 6 7 8 9 

gap=1 j=5 i=2
1 2 3 4 5 6 7 8 9 

gap=1 j=5 i=1
1 2 3 4 5 6 7 8 9 

gap=1 j=6 i=6
1 2 3 4 5 6 7 8 9 

gap=1 j=6 i=5
1 2 3 4 5 6 7 8 9 

gap=1 j=6 i=4
1 2 3 4 5 6 7 8 9 

gap=1 j=6 i=3
1 2 3 4 5 6 7 8 9 

gap=1 j=6 i=2
1 2 3 4 5 6 7 8 9 

gap=1 j=6 i=1
1 2 3 4 5 6 7 8 9 

gap=1 j=7 i=7
1 2 3 4 5 6 7 8 9 

gap=1 j=7 i=6
1 2 3 4 5 6 7 8 9 

gap=1 j=7 i=5
1 2 3 4 5 6 7 8 9 

gap=1 j=7 i=4
1 2 3 4 5 6 7 8 9 

gap=1 j=7 i=3
1 2 3 4 5 6 7 8 9 

gap=1 j=7 i=2
1 2 3 4 5 6 7 8 9 

gap=1 j=7 i=1
1 2 3 4 5 6 7 8 9 

gap=1 j=8 i=8
1 2 3 4 5 6 7 8 9 

gap=1 j=8 i=7
1 2 3 4 5 6 7 8 9 

gap=1 j=8 i=6
1 2 3 4 5 6 7 8 9 

gap=1 j=8 i=5
1 2 3 4 5 6 7 8 9 

gap=1 j=8 i=4
1 2 3 4 5 6 7 8 9 

gap=1 j=8 i=3
1 2 3 4 5 6 7 8 9 

gap=1 j=8 i=2
1 2 3 4 5 6 7 8 9 

gap=1 j=8 i=1
1 2 3 4 5 6 7 8 9 

1 2 3 4 5 6 7 8 9 
Process finished with exit code 0

三/🧐还有一个问题—GAP怎么界定?

上面我们直接把gap定为4,是出于最简单的一种考虑——把数组一分为2,由于数组长度为9,则9/2=4。然而这样的想法是最优的吗?🤫😏很遗憾不是的,这里介绍一个Knuth序列(Donald.Knuth发明),

h=3*h+1

以此来界定间隔序列会效率更高(别问我为啥,我也没搞懂,先记住)h尽可能取大,直到要到数组长度的三分之一了则停止,作为初始的gap值,采用Knuth序列的sort方法改成如下:

static void sort(int[] arr) {
        int h = 1;
        while (h <= arr.length / 3) {
            h = h * 3 + 1;
        }                                                 //得出h=4,真巧
        for (int gap = h; gap > 0; gap = (gap - 1) / 3) {
            for (int j = gap; j < arr.length; j++) {
                for (int i = j; i > gap - 1; i -= gap) {
                    if (arr[i] < arr[i - gap]) {
                        swap(arr, i, i - gap);
                        System.out.println("gap=" + gap + " j=" + j + " i=" + i);
                        print(arr);
                        System.out.println("\n");
                    } else {
                        System.out.println("gap=" + gap + " j=" + j + " i=" + i);
                        print(arr);
                        System.out.println("\n");
                    }
                }
            }
        }
}

四/😎时间复杂度、空间复杂度及稳定性

  • 希尔排序时间复杂度的最好情况是O(n^1.3),最坏是O(n ^2)
  • 希尔排序并没有用到额外的空间,因此空间复杂度是O(1)
  • 希尔排序是不稳定的,就是说数值相同的元素经过希尔排序后可能会调换顺序。
    看这样一个例子,数组int[] arr={6,1,2,6,4,3},用Knuth序列定义gap=4
    在这里插入图片描述
    在第一轮排序后橙色的6和4交换,就到了紫色6的后面,最后结果:
    在这里插入图片描述
    图片来源:https://baijiahao.baidu.com/s?id=1644158198885715432&wfr=spider&for=pc
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值