超详解排序算法之(四)希尔排序

希尔排序是插入排序的改进版,通过增量分组减少元素移动次数。文章详细介绍了希尔排序的基本思想、代码实现(交换法和移动法)以及效率分析,展示了如何提高排序效率,并通过测试数据验证了优化后的希尔排序在处理大量数据时的优势。

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

希尔排序

引入希尔排序

我们先看插入排序遇到的一种情况[插入排序可以出门左转,看上一篇内容],假定有一个数组[2,3,4,5,6,1],我们用插入排序的时候,我们只是将1插入这个数组最前边就可以,但是我们之前需要一步步从3、4、5、6,才能操作我们最终定序的这个1,这个1我们依次向左每遇到一个数字就要前移一下。简单插入排序遇到的问题就是:当需要插入的数是较小的数时,后移的次数明显增多,对效率有影响。
希尔排序是对于简单插入排序的改进,也称为缩小增量排序。

希尔排序的基本思想

希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,正儿文件恰被分成一组,算法便终止。


图解说明一下
在这里插入图片描述

代码实现

交换法结论代码

/**
* @param array 待排序的数组
*/
public static void shellSortByChange(int[] array){
	int temp=0;
	for(int step=array.length/2;step>0;step=step/2){
		for(int i=step;i<array.length;i++){
			for(int j = i-step;j>=0;j-=step){
				if(array[j]>array[j+step]){
					temp=array[j];
					array[j]=array[j-step];
					array[j-step]=temp;
				}
			}
		}
	}
}

分布代码详解实现

int temp=0;//声明一个标量作为临时变量进行交换处理

//第一轮 每组的步长为5
for(int i=5;i<array.length;i++){
	for(int j=i-5;j>=0;j-=5){//这儿才开始对于每一组开始做处理,通过步长从索引下标0开始
		if(array[j]>array[j+5]){
			temp = array[j];
			array[j]=array[i+5];
			array[j+5]=temp;
			}
	}
}
System.out.println("第一轮分组循环之后的情况"+ Arrays.toString(array));

// 第二轮 每组的步长为2
for(int i=2;i<array.length;i++){
	for(int j=i-2;j>=0;j-=2){//这儿才开始对于每一组开始做处理,通过步长从索引下标0开始
		if(array[j]>array[j+2]){
			temp = array[j];
			array[j]=array[i+2];
			array[j+2]=temp;
			}
	}
}
System.out.println("第二轮分组循环之后的情况"+ Arrays.toString(array));

// 第三轮 每组的步长为1
for(int i=1;i<array.length;i++){
	for(int j=i-1;j>=0;j-=1){//这儿才开始对于每一组开始做处理,通过步长从索引下标0开始
		if(array[j]>array[j+1]){
			temp = array[j];
			array[j]=array[i+1];
			array[j+1]=temp;
			}
	}
}
System.out.println("第三轮分组循环之后的情况"+ Arrays.toString(array));

归纳总结形成 "通项公式"

  1. 我们通过对于原始序列不断进行二分进行分组,当分组之后的步长>0 的时候我们就继续分,知道步长为1做最后一轮的处理,即简单插入排序,我们用一个for循环获取步长:for(int step=array.length/2;step>0;step/=2)
    2.对于每一组,我们通过组内进行交换处理,即插入排序的处理,从每一组的第二个元素开始进行,第一个元素就是操作元素的下标-步长。
    /**
    * 对于步长为2的分析
    * step=2,外层的处理为分组的情况对于每一组从第二位开始
    *
    *
    */
    for(int i=step;i<array.length;i++){//即分别对每一组的第二个元素开始进行递增,每一组第2为、第3位。。。
    for(int j=i-step;j>=0;j-=step){//通过与上边的关系,获取对应的组从下标0、1,、2.。。开始的元素与前边元素的比较
    }
    }
    这的处理类似于选择排序的思想
    所以我们最终的归纳代码即为:
int temp=0;
int count=0;
for(int step = array.length/2;step>0;step/=2){
            for(int i=step;i<array.length;i++){
                for (int j = i-step; j >= 0; j-=step) {//j=i-step;j<array.length;j+=step
                    if(array[j]>array[j+step]){//array[j]<array[j-step]
                        temp=array[j];
                        array[j]=array[j+step];
                        array[j+step]=temp;
                    }

                }

            }
            //打印出每一次分组之后的排序结果
            System.out.println("第"+(++count)+"轮分组循环之后的情况"+ Arrays.toString(array));
        }

我们代码测试一下是否符合我们的分析的结果
在这里插入图片描述
说明我们的代码没有问题


上述的处理我们采用的是交换法,我们看看交换处理的效率怎么样
直接上代码和测试结果

public static void main(String[] args) {

        int[] arr = new int[80000];
        for (int i = 0; i < arr.length; i++) {
            arr[i]=(int)(Math.random()*800000);
       }
        System.out.println("希尔排序!!");

       Date dateStart = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("Front of Execute:"+simpleDateFormat.format(dateStart));

       shellSort(arr);

       Date dateEnd = new Date();
        System.out.println("Front of Execute:"+simpleDateFormat.format(dateEnd));

结果
在这里插入图片描述
我们看到好像比插入排序还需要更长的时间,不是说希尔排序是对插入排序的改进么?越改越回去了??小伙伴猜猜问题出在哪里?花点时间考虑一下哦。
下边就是答案了。


我们对于每一组内找到的内容都是进行交换处理的,这样的效率并不高,所以我们只需要将之前做交换的部分处理为插入的方式,我们再看看效率。具体的推导在此就不在赘述,如果有需要,请留言。看到会回复。

希尔排序移动法

public  static  void shellSort2(int[] array){
        int count=0;
        for(int gap=array.length/2;gap>0;gap/=2){//同样先分组
            //从第gap个元素,逐个对其所在的数据进行直接插入排序
            for(int i= gap;i<array.length; i++){
                int j=i;
                int temp = array[j];
                if(array[j]<array[j-gap]){

                    while(j - gap >=0 && temp < array[j-gap]){
                        array[j]=array[j-gap];
                        j-=gap;
                    }
                    //当跳出循环的时候,就表示找到了插入的位置
                    array[j]=temp;

                }

            }

            System.out.println("第"+(++count)+"轮分组循环之后的情况"+ Arrays.toString(array));
        }

    }

我们先验证一下我们这段代码的正确性
用同样的数据{8,9,1,7,2,3,5,4,6,0},上结果
在这里插入图片描述
接着我们进行测试一把80000个数据的处理效率是否有提升

	public static void main(String[] args) {
        int[] arr = new int[80000];
        for (int i = 0; i < arr.length; i++) {
            arr[i]=(int)(Math.random()*800000);
        }
        System.out.println("希尔排序!!");

        Date dateStart = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("Front of Execute:"+simpleDateFormat.format(dateStart));

        shellSort2(arr);

        Date dateEnd = new Date();
        System.out.println("Front of Execute:"+simpleDateFormat.format(dateEnd));

    }
    /**
     * 移动法
     * @param array
     */
    public  static  void shellSort2(int[] array){
        int count=0;
        for(int gap=array.length/2;gap>0;gap/=2){//同样先分组
            //从第gap个元素,逐个对其所在的数据进行直接插入排序
            for(int i= gap;i<array.length; i++){
                int j=i;
                int temp = array[j];
                if(array[j]<array[j-gap]){

                    while(j - gap >=0 && temp < array[j-gap]){
                        array[j]=array[j-gap];
                        j-=gap;
                    }
                    //当跳出循环的时候,就表示找到了插入的位置
                    array[j]=temp;

                }

            }

//            System.out.println("第"+(++count)+"轮分组循环之后的情况"+ Arrays.toString(array));
        }

    }

结果展示
在这里插入图片描述
秒级的处理,感兴趣的小伙伴可以将时间精度改为毫秒级,看下对应的处理速度,然后试试80W个数据的处理速度。希尔排序到此先告一段落。具体的时间依据个人电脑的状态可能有所不同,可以多测几次,平均值,80w条数据不到1s的时间吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值