"简单"的优化--希尔排序也没你想象中那么难

写在前边

大家好,我是melo,一名大二上软件工程在读生,经历了一年的摸滚,现在已经在工作室里边准备开发后台项目啦。
不过这篇文章呢,还是想跟大家聊一聊数据结构与算法,学校也是大二上才开设了数据结构这门课,希望可以一边学习数据结构一边积累后台项目开发经验。
最近我们进入了排序算法专题,上节课聊到了"简单"插入排序,那在简单的基础上,我们可以怎么做进一步的优化呢,这篇来看看优化版--希尔排序

知识点

概念

希尔排序(Shell Sort)是插入排序的一种,它是针对直接插入排序算法的改进。
希尔排序又称缩小增量排序,因 DL.hell 于 1959 年提出而得名。
它通过比较相距一定间隔的元素来进行,各趟比较所用的距离随着算法的进行而减小,直到只比较相邻元素的最后一趟排序为止。

引入

简单插入排序存在问题

image.png

改进

  • 分割待排序记录的个数,分别进行插入排序

image.png

基本思想

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

精髓

  • 由于开始时每组只有很少整数,所以排序很快。之后每组含有的整数越来越多,但是由于这些数也越来越有序,所以排序速度也很快。

示意图

按一定增量分组,然后逐渐减小增量

初始化gap为length/2,逐渐减小为gap/2,直到gap不满足>0的条件

分组后,再对该组进行简单插入排序

  • 拿图中的第三步举例,数组分成了两组[3,1,0,9,7],[5,6,8,4,2]
    • 对[3,1,0,9,7]进行简单插入排序,看成前n-1个为有序数组,第n个为待插入的元素(找到自己的位置后插入即可)

image.png

不够清晰的话也可以看下边这张


代码实现

力扣912排序数组 : https://leetcode-cn。com/problems/sort-an-array/submissions/
又是这道题hhh,万能

思路概览

首先

  • 我们要先初始化增量gap=length/2,然后不断缩小gap=gap/2 直到不满足gap>0

所以我们最外层会需要一个for循环来调控这个gap的变化

其次,再往内层走

  • 对于分组后,由于我们是要对分组后的每一组进行简单插入排序,而插入排序我们默认从待排序数组的第二位开始,所以我们需要从每一组的第二位开始去遍历,直到整个数组的末尾

for循环让i=gap;i<数组;i++即可

最后,就对该数组进行插入排序即可

  • 注意不是跟前一个进行比较了,而是跟 j-gap 比较

最初版本(for)

for的话,我是先把j赋值等于i-gap,这样的话就是跟j去比较,最后也还会去j-=gap
会导致我最后跳出循环的时候,得插到j+gap

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int* sortArray(int* nums, int numsSize, int* returnSize){
    //逐步缩小增量gap
    for(int gap=numsSize/2;gap>0;gap=gap/2){
        int insertValue = 0;
        int j;
        //从分组后的第一组的第二位开始
        for(int i=gap;i<numsSize;i++){
                //保存待插入的值
                insertValue=nums[i];
                //因为本身有序,若待插入的数还大于最后一个数,则无须继续遍历下去了
            	//注意j>=0的条件,这里无哨兵了
                for(j=i-gap;j>=0 && insertValue<nums[j];j-=gap){
                    //若待插入的值小于索引值,证明要索引值需要后移,空出j这个位置给插入值
                        nums[j+gap]=nums[j];
                }
                //跳出循环后,把这个数插入到指定位置
                nums[j+gap]=insertValue;
        }
    }
    *returnSize=numsSize;
    return nums;
}

改进for

先去判断是否 j-gap>=0,满足才进循环,才会去j-=gap,所以最后j就是要插入的位置

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int* sortArray(int* nums, int numsSize, int* returnSize){
    //逐步缩小增量gap
    for(int gap=numsSize/2;gap>0;gap=gap/2){
        int insertValue = 0;
        //用于插入排序中遍历待排序的数组
        int j;
        //从分组后的第一组的第二位开始
        for(int i=gap;i<numsSize;i++){
                //保存待插入的值
                insertValue=nums[i];
        //因为本身有序,若待插入的数还大于最后一个数,则无须继续遍历下去了
                for(j=i;j-gap>=0 && insertValue<nums[j-gap];
                    j-=gap){
                    //若待插入的值小于索引值,证明要索引值需要后移,空出j这个位置给插入值
                        nums[j]=nums[j-gap];
                }
                //跳出循环后,把这个数插入到指定位置
                nums[j]=insertValue;
        }
    }
    *returnSize=numsSize;
    return nums;
}

注意

  • 希尔排序没有办法用到哨兵了,我们需要注意判断是否走到头了

参考

  • 菜鸟教程
  • 尚硅谷数据结构与算法
posted @ 2021-10-21 13:38  Melo~  阅读(165)  评论(0编辑  收藏  举报