希尔排序

希尔排序

我们知道,插入排序的原理是将一个数组看成两段,一段有序的,一段无序的,

每次将无序的数字中第一个数在有序的一段中找到合适位置插入

插入排序有一个特征就是如果数组呈现接近有序,那么排序的速度就会很快

如:5, 1, 2, 7, 9, 8

只需将1插到5前面,2插到1、5中间,7不动,9不动,8插入7、9中间

但是,如果出现较小的元素被放置在数组的末端,就会产生很多移动数组元素的开销

如:5,4,9,8,1,2

这时我们就需要用到希尔排序,希尔排序也是一种插入排序,目的是将数组中的数字

尽量呈现有序的分布

希尔排序示意图

第一轮

第二轮

简单来说就是,先从多分组、小范围起调整元素的大小分布,然后依次减少分组,增大范围,调整元素分布

每次将当前元素的数组索引之前所有属于同组的元素进行依次比较,将较大的元素往后放,较小的元素往前放

如:假如代码处在第二轮排序,数组从数字1开始遍历,此时前面只有3是同组元素,就和3比较,需要交换位置

此时组内元素分布为:1,3,0,9,7

遍历到0时,此时前有1、3是同组元素,则先比较0和3,需要交换0和3的位置,

此时组内元素分布为:1,0,3,9,7

然后比较1和0,需要交换1和0的位置,

此时组内元素分布为:0,1,3,9,7

这时,索引4及其前面的所有组内元素的分布顺序已经调整完毕,后面依次类推

代码实现

交换法

类推

//希尔排序第一轮,将10个数组分成了5组
        for (int i = 5; i < arr.length; i++) {
            //分成5组,那么每组内相邻数字的索引差为5
            //因为采用交换法,类似冒泡,依次将相邻元素进行比较(类似一个窗口,窗口的两端是要比较的数,比较完后窗口向前移动)
            //窗口两端比较的数一定是在同一组的,且是相邻的,遍历第一组时,窗口大小为组内相邻数的索引差5
            //第一组左端索引0,右端索引5,下一组左端1,右端6,依次向前移动窗口
            for (int j = i - 5; j >= 0; j -= 5) {
                //窗口的滑动,就是在组内,尽量将较大或者较小的元素后移,使组内元素尽量呈现有序
                //j就是左端的索引,i就是右端索引
                //刚开始j=0,i=5
                //第二层for循环就是调整数组中每个元素在其所属组内所在位置之前的所有元素(并包括自己)进行调整有序
                if (arr[j] > arr[j + 5]) {
                    temp = arr[j];
                    arr[j] = arr[j + 5];
                    arr[j + 5] = temp;
                }
            }
        }


> [3, 5, 1, 6, 0, 8, 9, 4, 7, 2]
//希尔排序第二轮,将10个数组分成了5/2=2组
        for (int i = 2; i < arr.length; i++) {
            for (int j = i - 2; j >= 0; j -= 2) {
                //说明需要调整顺序,前面较大的数需要向后放
                if (arr[j] > arr[j + 2]) {
                    temp = arr[j];
                    arr[j] = arr[j + 2];
                    arr[j + 2] = temp;
                }
            }
        }


> [0, 2, 1, 4, 3, 5, 7, 6, 9, 8]
//希尔排序第三轮,将10个数组分成了5/2/2=1组
        for (int i = 1; i < arr.length; i++) {
            for (int j = i - 1; j >= 0; j -= 1) {
                //说明需要调整顺序,前面较大的数需要向后放
                if (arr[j] > arr[j + 1]) {
                    temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }

> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

最终实现

for (int gap = arr.length / 2; gap > 0; gap /= 2) {//控制分组数,分组数最小为1
            //遍历各组中所有
            for (int i = gap; i < arr.length; i++) {//i为各种分组下,第一个分组内的第二个元素的索引
                for (int j = i - gap; j >= 0; j -= gap) {//遍历前面的组内元素
                    //说明需要调整顺序,前面较大的数需要向后放
                    if (arr[j] > arr[j + gap]) {
                        temp = arr[j];
                        arr[j] = arr[j + gap];
                        arr[j + gap] = temp;
                    }
                }
            }
        }

插入法

public static void shellSort2(int[] arr){
        int insertVal = 0;
        int insertIndex = 0;
        for (int gap = arr.length / 2; gap > 0; gap /= 2) {
            //从gap个元素,逐个对其所在的组进行直接插入排序
            for (int i = gap; i < arr.length; i++) {
                insertVal = arr[i];
                insertIndex = i-gap;
                if(insertVal<arr[insertIndex]) {
                    while (insertIndex >= 0 && insertVal < arr[insertIndex]) {
                        arr[insertIndex + gap] = arr[insertIndex];
                        insertIndex -= gap;
                    }
                    arr[insertIndex + gap] = insertVal;
                }

            }
        }

普通插入排序

for (int i = 1; i < arr.length; i++) {
            insertVal = arr[i];
            insertIndex = i - 1;
            while (insertIndex >= 0 && insertVal < arr[insertIndex]) {
                arr[insertIndex + 1] = arr[insertIndex];
                insertIndex--;
            }
            //假如待插入值是12,理应插入位应在4的后面,循环会在4的索引停住,则需要插入到insertIndex+1
            if(insertIndex!= (i-1)){
                arr[insertIndex + 1] = insertVal;
            }
        }

可以发现,希尔排序跟插入排序十分相似

  • 普通的插入排序,就相当于在希尔排序中直接分为一组,在所有元素中查找插入位置
  • 而希尔排序是先分成更多的组,缩小插入排序的规模
  • 普通的插入排序是在n-1个元素中找插入位置,并且可能会将n-1个元素向后移动
  • 而希尔排序是先从2个元素之间进行插入,然后在4个元素之间进行插入,然后依次在8、16、32个元素之中插入
  • 但是在插入排序的插入范围依次增大时,元素已经逐渐呈现有序,越到后面,在插入时需要移动元素的个数就越来越少

希尔排序的写法思路:可以先将普通的插入排序写出来,然后在外层再套一层循环,然后替换插入排序中的所有

posted @ 2021-03-11 18:29  编程の小白  阅读(74)  评论(0编辑  收藏  举报