希尔排序

一:基本思想

先选定一个整数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循环中改变自己大小,控制不住 !

相关推荐
tinker在coding13 分钟前
Coding Caprice - Linked-List 1
算法·leetcode
XH华5 小时前
初识C语言之二维数组(下)
c语言·算法
南宫生5 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
不想当程序猿_5 小时前
【蓝桥杯每日一题】求和——前缀和
算法·前缀和·蓝桥杯
落魄君子5 小时前
GA-BP分类-遗传算法(Genetic Algorithm)和反向传播算法(Backpropagation)
算法·分类·数据挖掘
菜鸡中的奋斗鸡→挣扎鸡5 小时前
滑动窗口 + 算法复习
数据结构·算法
Lenyiin6 小时前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
郭wes代码6 小时前
Cmd命令大全(万字详细版)
python·算法·小程序
scan7246 小时前
LILAC采样算法
人工智能·算法·机器学习
菌菌的快乐生活6 小时前
理解支持向量机
算法·机器学习·支持向量机