排序----希尔排序

news/2024/9/24 7:06:12/
void ShellSort(int* a, int n)
{int gap = n;while (gap > 1){// +1保证最后一个gap一定是1// gap > 1时是预排序// gap == 1时是插入排序gap = gap / 3 + 1;for (size_t i = 0; i < n - gap; ++i){int end = i;int tmp = a[end + gap];while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = tmp;}}
}

以上为完成代码实现,以下为详细讲解。

首先,希尔排序有两个过程。

1.预排序:让数组接近有序。

2.插入排序

(默认元素都存在数组a中,一共有a个元素)

        预排序,即取一个gap值,gap至今并未直接证明取什么值最好,但大部人都同意gap=gap/3+1(gap初始化为n)效率最高。

        为什么gap取值是变化的?因为:

        gap越大,大的元素就可以越快跳到后面,小的元素就可以越快跳到前面,但是越不接近有序。gap越小,元素跳的越慢,但是越接近有序。当gap=1的时候就相当于插入排序,数组就有序了。所以我们期望gap取变化的值,那么就可以兼顾他们的优点。即gap取值越来越小可以实现这个想法。

        那么gap取值为什么最后+1呢?因为:

        gap/3的结果可能为0.1.2,那么最后一次就有可能不能实现插入排序,导致最后排序的数组并不是完全有序的。+1是为了保证最后一次一定是gap=1,那么就一定可以实现插入排序,那么数组就一定有序了。

        将所有数据分成gap组,每组内的元素间相隔gap个位置,那么每组就有(n/gap=)3个数据(忽略gap取值中的+1)。然后分别将每一组中的元素进行排序。

接下来我们逐步写代码:

首先排一组:

        我们先排红色这一组,我们需要注意的是i的终止位置,同时要注意,我们是用i这个位置的数据与下一个位置的数据来比较大小。数组中最后一个元素的下标为n-1,而且该元素恰好为这一组中的最后一个元素,那么倒数第二个元素的下标为n-1-gap,所以i<n-gap。然后要注意到的是内层while循环的条件,如果下一个位置的数据tmp比end这个位置的数据小,那么我们还要比较tmp是否比end-gap位置的数据小,如果还小,那么end就要一直前移gap个位置,最差的情况是end移动到了0-gap这个位置,就说明此时tmp是它及其它之前的所有元素中最小的那个,那么只需要把end+gap=0这个位置的数据赋值上tmp(0+gap及其之后的元素在while循环中已经被赋值完成了)。

//然后我们想对gap组数据进行排序

        关键点就是end一开始的取值,我们在外面再加一层for循环,来为end赋初值。

//但是我们觉得这样三层循环有点冗余。

        这种写法与上一种写法的比较次数是一样的。这种写法是每一组的第一二个元素都比较完了,再比较每一组的第二三个元素,那么最后一组就是n-gap-1与n-1相比,那么就把两层for循环变成了一层for循环。

        这一种是多组并着走,上一种是一组一组走。

//最后,我们在最外层再加上while循环,来让gap成为一个不断变化的值。

最后,希尔排序的时间复杂度是O(N^1.3),这是一个大约值,记住就行了,这个特别难算。

//接下来,简单讲一下这个时间复杂度为何难算:

        取gap=n/3(忽略+1,影响不大),那么每一趟比较的消耗=每组比较次数*组数。

        最坏情况下,第一趟预排序的消耗:(1+2)*(n/3)=n  。将这三个元素看成逆序的,第二个元素交换一次,第三个元素交换两次。gap就是组数。

        最坏情况下,第二趟预排序的消耗:(1+2+3+4+5+6+7+8)*(n/9)=4*n 。第二趟gap=n/3/3=n/9,每组9个数据。看成逆序排列,同上一段的讲述。

        但是要注意的问题是,每一趟都比前一趟更接近有序,那么就不会是最坏情况下的消耗。

        最后一趟已经非常接近有序了,此时gap=1,也就是直接插入排序的消耗:n 。

        同时,gap也是变化的值,主导元素一直在变化,导致时间复杂度无法精确的求出来。

        那么每一趟的消耗呈现为下面这样的关系:

完结~撒花~


http://www.ppmy.cn/news/1529705.html

相关文章

企业EMS -能源管理系统-能源管理系统源码-能源在线监测平台

能源管理系统是以帮助工业生产企业在扩大生产的同时&#xff0c;合理计划和利用能源&#xff0c;降低单位产品能源消耗&#xff0c;提高经济效益&#xff0c;降低CO2排放量为目的信息化管控系统。 我国能源管理从上世纪80年代中期开始&#xff0c;通过“能量平衡测试”、“能源…

HDL coder使用手册

&#x1f4a1; 由于本科毕设女朋友准备使用FPGA完成&#xff0c;因此写这篇文章帮助她快速上手HDL coder的使用&#xff0c;降低前期入门的难度。 支持生成HDL代码的simulink库 名字中含有HDL的库中的模块一般都可以用来生成HDL代码。直接搜索模块名称&#xff0c;比如搜索fir&…

662. 二叉树最大宽度 BFS 力扣

662. 二叉树最大宽度 已解答 中等 相关标签 相关企业 给你一棵二叉树的根节点 root &#xff0c;返回树的 最大宽度 。 树的 最大宽度 是所有层中最大的 宽度 。 每一层的 宽度 被定义为该层最左和最右的非空节点&#xff08;即&#xff0c;两个端点&#xff09;之间的长…

vue入门小练习

文章目录 1.案例需求2.编程思路3.案例源码4.小结 1.案例需求 一个简易的计算器&#xff0c;其效果如下&#xff1a; 图片切换&#xff0c;其效果如下&#xff1a; 简易记事本&#xff0c;其效果如下&#xff1a; 2.编程思路 1.这个Vue.js应用实现了一个简单的计算器&#x…

黑马智数Day3

渲染基础Table列表 封装接口&#xff1a; export function getCardListAPI(params) {return request({url: /parking/card/list,params}) } 具体实现&#xff1a; import { getCardListAPI } from /apis/cardexport default {data() {return {// 请求参数params: {page: 1,pa…

委托的注册及注销+观察者模式

事件 委托变量如果公开出去&#xff0c;很不安全&#xff0c;外部可以随意调用 所以取消public,封闭它&#xff0c;我们可以自己书写两个方法&#xff0c;供外部注册与注销&#xff0c;委托调用在子方法里调用&#xff0c;这样封装委托变量可以使它更安全&#xff0c;这个就叫…

C语言指针篇

要想学好C语言&#xff0c;作为灵魂的指针那是必须要掌握的&#xff0c;而要想搞定指针&#xff0c;就不得不讲一下内存和地址之间的关系 内存和地址 计算机上的CPU&#xff08;中央处理器&#xff09;在处理数据的时候&#xff0c;需要的数据是在内存中读取的&#xff0c;处…

0-1开发自己的obsidian plugin DAY 1

官网教程有点mismatch&#xff0c;而且从0-100跨度较大&#xff0c;&#x1f4dd;记录一下自己的踩坑过程 首先&#xff0c;官网给的example里只有main.ts&#xff0c;需要自己编译成main.js 在视频教程&#xff08;https://www.youtube.com/watch?v9lA-jaMNS0k&#xff09;里…