希尔排序

一:基本思想

先选定一个整数gap,把待排序文件中所有记录分成个组,按照所有距离为整数gap的记录分在同一组内,并对每一组内的记录进行排序。然后,通过整数gap逐渐变小,重复上述分组和排序的工作。当整数gap变小到达=1时,也就是执行插入排序,这样所有记录在统一组内就排好序了

所以一定得先理解:插入排序-CSDN博客

不然很难理解此博客

二:预排序的意义

**Q:**既然最后都要执行插入排序,那多一步预排序,不是会徒增运算了

**A:**预排序的意义在于让最后一步的插入排序的运算量大大的减小,比直接的单独的插入排序更优

三:预排序的核心

正如希尔排序的思想,我们需要一个整数gap,这个整数gap会逐渐变小,最终变小到1为止

一般是gap 初始化为N,也就是数组的元素个数,然后在循环中 gap= gap/2 ,这样每次进入循环这个gap就 /2,会逐渐的减小,最终为1(跟着除法原则,一个大于2的数不断的/2,一定会有一次为1)

例子:

解释:

1:

gap = N(10);

gap = gap/2 = 5;

根据思想可知:gap为5,即按照所有距离(下标差值)为5的记录分在同一组内,如图内的:

第一组:9 4

第二组:1 8

第三组:2 6

第四组:5 3

第五组:7 5

然后每组进行组内的排序:

第一组:4 9

第二组:1 8

第三组:2 6

第四组:3 5

第五组:5 7

也就是上图中的:

2:

gap = gap/2 = 2;

此时的gap变成2,所以:

第一组:4 2 5 8 5

第二组:1 3 9 6 7

然后进行每组的组内排序:

第一组:2 4 5 5 8

第二组:1 3 6 7 9

也就是上图中的:

3:

gap = gap/2 = 1;

gap为1:

只有一组:2 1 4 3 5 6 5 7 8 9

组内排序后:

1 2 3 4 5 5 6 7 8 9

也就是上图中的:

说白了gap为1 的时候,进行的就是一次插入排序,而且可以看的出来,最后一次插入排序之前 ,我们接收到的数组已经有了一定的顺序。

下面是博主找到的一个动态演示图:不过数据和上面的不一样,一样的也是gap =5 到 gap = 2再到最后的 gap =1:

四:代码展示

cpp 复制代码
//希尔排序的第一种写法(双for)
void ShellSort(int* arr, int N)
{
	//gap初识为N,元素的个数
	int gap = N;
	//gap不为1就要继续的缩小并排序
	while (gap > 1)
	{
		//gap缩小
		gap = gap / 2;
		//这个for控制每组的元素
		for (int j = 0; j < gap; j++)
		{	//这个for控制每组内的排序
			for (int i = j; i< N - gap; i += gap)
			{
                int end = i;
				//即将排序的元素,保留在tmp
				int tmp = arr[end + gap];
				//end>=0代表还有元素未比较
				while (end >= 0)
				{
					if (tmp < arr[end])
					{
						arr[end + gap] = arr[end];
						end -= gap;
					}
					else
					{
						break;
					}
					//来到这里分为两种情况 
					//1:break->遇到比元素tmp小或和tmp相等的,将m放在它的后面
					//2:全部比较完了,都没有遇到<=tmp的,最后tmp放在数组第一个位置
					arr[end + gap] = tmp;
				}
			}
		}
	}
}

解释:

**1:**跟纯粹的插入排序相比,咱们多了一个控制每组的元素的for循环 ,以及多了一个while来确保gap最后为1执行完排序才终止

**2:**第二个for循环:

该for循环控制的是每组元素的内部排序,可以看作插入排序,不过元素是间隔的!i<N-gap和插入排序中的i<N-1意义一致,在插入排序中我们最后的end(i)要停留在倒数第二个元素是,下标为N-2,所以end才<N-1,才能取到N-2。所以们这里i<N-gap,也是为了确保end停留在倒数第二个元素上。

**3:**第一个for循环:

该for控制的是每组的元素,gap为5,数组被分成了5组,j会每次都赋给end,这样end的起始位置不同,也就进行的组的更换,再在第二个for中进行组内的排序:

如图所示:

gap为5的最终结果:

然后gap就会变小,进行新一轮的分组排序,最后gap =1 的那一次的分组排序执行完,就获得了一个有序的数组,这就是希尔排序。

希尔排序还有另一种写法:

cpp 复制代码
//单for
void ShellSort(int* arr, int N)
{
	//gap初识为N,元素的个数
	int gap = N;
	//gap不为1就要继续的缩小并排序
	while (gap > 1)
	{
		//gap缩小
		gap = gap / 2;
		for (int i= 0; i< N - gap; i++)
		{
            int end = i;
            int tmp = arr[end + gap];
			while (end >= 0)
			{
				if (tmp < arr[end])
				{
					arr[end + gap] = arr[end];
					end -= gap;
				}
				else
				{
					break;
				}
				arr[end + gap] = tmp;
			}
		}
	}

}

第一种写法是:分一组,排序一组,然后再去分一组,再排序

第二种写法是:多组并排,也就是直接对数组选择性的进行排序

如图:

end =1 就进行①的两个元素的排序,以此类推

最后得到:

'

然后再进行gap的缩小,进行新一轮的排序

个人觉得双for循环的写法更加的易懂

五:效果测试

六:与插入函数的比较

相信很多人都并觉得希尔比插入好,下面进行比较运算的时间(单位ms)来展示希尔的强大:

测一万个随机数,插入排序花了6ms,希尔花了1ms

测十万个随机数,插入排序花了0.6秒,希尔花了9ms

测一百万个随机数,插入排序花了63秒,希尔花了0.1秒,我就问你屌不屌??

七:一些容易出错的细节

**1:**gap = gap/2,对于N比较大的时候不太够看,建议gap = gap/3+1,+1是为了确保gap可以为1

**2:**千万不要觉得end 的值 要经过 i 的复制,感觉太过麻烦,直接把end写在for循环那里,这样会造成 end在for循环中改变自己大小,控制不住 !

相关推荐
网易独家音乐人Mike Zhou3 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
Swift社区6 小时前
LeetCode - #139 单词拆分
算法·leetcode·职场和发展
Kent_J_Truman7 小时前
greater<>() 、less<>()及运算符 < 重载在排序和堆中的使用
算法
IT 青年7 小时前
数据结构 (1)基本概念和术语
数据结构·算法
Dong雨8 小时前
力扣hot100-->栈/单调栈
算法·leetcode·职场和发展
SoraLuna8 小时前
「Mac玩转仓颉内测版24」基础篇4 - 浮点类型详解
开发语言·算法·macos·cangjie
liujjjiyun8 小时前
小R的随机播放顺序
数据结构·c++·算法
¥ 多多¥8 小时前
c++中mystring运算符重载
开发语言·c++·算法
trueEve9 小时前
SQL,力扣题目1369,获取最近第二次的活动
算法·leetcode·职场和发展
天若有情6739 小时前
c++框架设计展示---提高开发效率!
java·c++·算法