四、数组-希尔排序

本文深入解析希尔排序算法,探讨其作为插入排序的一种优化方法如何通过引入步长概念减少数据远距离移动,提高排序效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

希尔排序是插入排序的优化版,我们知道插入排序是普通排序,在两两比较的前提下,进行的排序算法。这样有一个问题,如果数组极端情况下全是逆序,比如[j,i,h,g,f,e,d,c,b,a],要完成升序的排序的话,会让比较和交换的次数为N*N/2次。如果这个数据量越大,移动的距离就更长,

那么希尔排序如何优化插入排序呢,它解决了插入排序中,数据逐个比较产生的坏情况下的远距离移动 。使用的方法是通过跳跃式的选取节点数据进行排序。比如对10个数据项,会使用 048,159,26,37这四组下标,进行下标内部排序,然后再按照1的间距进行排序,这样就保证了,最后进行1间距排序的时候,不会产生超远距离的移动。

在第一次048,159,26,37这四组下标的时候,048下标的三个元素进行了远距离的比较,虽然步长——对,请记住这个名词,步长。虽然步长大,但是因为数据量很小,只有三个,所以很快。然后接着比较159这三个元素,同样的处理方式。接着比较其他组的元素。

比较完成之后,然后缩小步长,我们使用两两比较,再把整个数组进行比较,这样就比较快了。刚开始我会想,有一种情况:如果048下标刚好是c,b,a这三个元素,即使是完成了第一次步长的排序,也还是会变成【a,x,x,x,b,x,x,x,d,x】,那d岂不是还要移动6个位置去到达最终位置,这个可能是有的,因为这类特例是无法控制的。所以不能使用特例来讨论。

下面是代码:注意,我这里是1万的数据。。。。所以,普通排序可能会稍慢,你也可以实施10万的数据,大概是2分钟吧。建议copy下去,执行一下。sort为希尔排序,normalsort为冒泡排序。所以,如果太急的话,可以只看上面的sort方法。

public class Shellsort {

    public static void sort(int[] ints) {
        System.out.println("希尔排序前,前6个元素");
        for(int i = 0 ; i<6 ; i++){
            System.out.print(ints[i]+"\t");
        }
        long startTime=System.currentTimeMillis();
        int inner, outer; 
        int temp;  //定义三个数据变量,在循环中接收比较的数据

        int h = 1;
        int length = ints.length;
        while (h <= length/3) {
            h = h * 3 + 1;
        }

        while (h > 0) {
           for (outer = h ; outer < length ; outer ++){
                temp = ints[outer];   //temp 获得outer的引用
                inner  =  outer;
                while (inner > h-1 && ints[inner-h] >= temp){ //如果前面的大于后面的,执行交换
                    ints[inner] = ints[inner-h]; //inner 在第一次进入while的时候= outer ,这是inner下标=outer,outer的引用被修改为大的值,outer原本指向的数据断开,由temp保存引用,作为最小值,在推出while的时候,赋值给这一个分组的最小的下标
                    inner -= h; //每次while循环,如果前面大于后面,则交换,同时修改inner所表示的下标,这时,前面进行的第一次排序也就失效,所以再次进入while,进行排序。
                }
                ints[inner] = temp; //如果这一组数据是多余两个的,需要在这一次完成多个数据排序。因为采用的是 h*3+1的数据,会存在 n-h>h-1的情况,比如增量为4的时候,8,9,10,11,12都是这样的数据。

           }
            h = (h-1)/3; //对增量进行缩减控制,刚开始增量大,分组多,数据距离大,但是数据项少。越到后面,增量小,分组少,数据项多,但是交换距离小。

        }
        long endTime=System.currentTimeMillis();
        System.out.println("\n\n希尔排序数据所用时间: "+(endTime-startTime)+"ms");
        for(int i = 0 ; i<6 ; i++){
            System.out.print(ints[i]+"\t");
        }
        System.out.println("");
    }

    public static void normalsort(int[] ints){
        System.out.println("\n\n冒泡排序前,前6个元素");
        for(int i = 0 ; i<6 ; i++){
            System.out.print(ints[i]+"\t");
        }
        long startTime=System.currentTimeMillis();
        int length  =  ints.length;
        for(int i =0 ; i<length; i++ ){
            for(int j= i+1 ; j<length; j++){
                if(ints[j] < ints[i]){
                    int temp = ints[j];
                    ints[j] = ints[i];
                    ints[i] = temp;
                }
            }
        }
        long endTime=System.currentTimeMillis();
        System.out.println("\n\n冒泡循环数据所用时间: "+(endTime-startTime)+"ms");
        for(int i = 0 ; i<6 ; i++){
            System.out.print(ints[i]+"\t");
        }
    }

    public static void main(String[] args){
        int length = 100000;

        int[] ints = new int[length];
        int[] normals =  new int[length];
        for(int n=0 ; n<ints.length ; n++){
            normals[n] =  ints[n] = (int)(Math.random()*2000000);
        }
        System.out.println("开始排序,数据量为"+length+"\n\n");
        sort(ints);
        normalsort(normals);
    }
}

如果觉得过于抽象,或者是我有注释没有说清楚,请参考图示:《10大经典排序》中的希尔排序图示。

数组中保存的元素都是数据的引用(基本数据类型保留的是值,数据本身),那么对于java而言,数组交换的实质是对引用的交换,而C是真实地对数据的值进行了交换,这点需要明确一下。所以C里面的交换成本理论上来讲,是大于java的交换成本的。

最后,一个问题,希尔排序的关键点在于步长的控制,如何选择合适的步长呢?请参考《数据结构与算法》第七章,241页的内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值