数据结构 排序(三) 直接插入排序、希尔排序

前几章讲了选择排序中的直直接选择排序、双向选择排序、堆排序,这次来讲讲利用‘插入’为核心来实现的插入排序算法。

插入排序

把待排序的记录按其关键码值的大小逐个插入到一
个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。

其实在我们的生活中,插入排序是随处可见的
在这里插入图片描述
例如打扑克牌时我们将发来的卡牌一张一张的插入进排序好的手牌中。整理图书或者档案时按照编号将新的书籍放入已排序号的书架。这就是插入排序的核心 “将未排序数据一个个插入进已排序的数据中


直接插入排序

直接插入排序就是对这个思路的简单应用,例如对于下面这个数据,对其排升序。

int arr[] = {46, 74, 53, 14, 26, 36, 86, 65, 27, 34};

如果要理解一个排序算法,我们首先要理解他的一趟思路。
在这里插入图片描述
对于上面这个数据,因为我们要排升序,按照前面的思路我们需要将未排序数列插入进已排序数列。红框框起来的地方就是按照升序排序好的序列,此时,因为74的下一个数据53比他小,所以我们要将他插入进这个已排序的数列中,并且在已排序的数列中找到合适的位置。

在这里插入图片描述
因为53比74小,所以往前移动, 然后46比53小,所以在46前停下,然后将74放到原本53的位置,形成新的有序数据。

第二趟
在这里插入图片描述
然后因为14比74小,将其插入进有序数列中,因为14比有序数列中的任何数都小,所以通过比较大小,然后被放到了数组最前端,然后将74放到原本14的位置。

总结下来就是将数列中已经排好的有序子串以此与后面的对比,然后将其插入到合适的位置。

这是一趟的思路,而多趟无非就是不断重复这个过程,直到排序结束。因为排N个数据,所以最坏情况下只需将n-1个数据插入就可以排序成功。

所以下面开始代码实现

void InsertSort(int* arr, int n)
{
	int i, end, temp;
	for(i = 0; i < n - 1; i++)
	{
		end = i;
		temp = arr[end + 1];
		//将插入元素保存下来, 从有序子串的尾部开始对比插入
		while(end >= 0)
		{
			if(arr[end] > temp)
			{
				arr[end + 1] = arr[end];
				end--;
			//如果插入的数据比这个位置的数据大,则比该数据大的数据后移,插入数据继续往前直到找到比他小得数据后停下
			}
			else
				break;
		}
		
		arr[end + 1] = temp;
		//将插入的元素放到最终所在的位置
	} 
}

在这里插入图片描述
每趟排序

直接插入排序
时间复杂度:平均情况:O(n) 最好情况O(n^2) 最坏情况O(n^2)
空间复杂度:O(1)


希尔排序

希尔排序是对直接插入排序的改进,也是最高效的插入排序。
他的核心就是一种分组预排序的思路:

1.预排序
2.直接插入排序

就是用gap分组预排序,再对每一组插入排序

什么意思呢?希尔排序引入了一个变量,gap(步长),将间隔为gap的元素分为一组,对每一组进行插入排序,然后不停减少gap的大小,既每次扩大分组,在部分有序的基础上再次进行分组插入排序,知道gap=1也就是只剩下一个分组时,排序完成

还是刚刚那些数据

int arr[] = {46, 53, 74, 14, 26, 36, 86, 65, 27, 34};
关于gap的选择

gap越大,越不接近有序,跳的越快
gap越小,越接近有序,跳的越慢

为了刚开始快速预排,所以一开始我们需要将gap设置大一点,实现部分有序,然后逐渐减少,实现全体有序。

一般gap = length / 3 + 1;
在这里插入图片描述
还是那句老话,理解排序首先理解单趟。

为了方便观看我将所有分组按照颜色不同来划分

首先46大于间隔gap的26,将这个分组内的26插入到46前。
下面同理

然后到交换后的46也就是第5个位置时,将27插入到子序列,26 ,46中的合适位置。
下面同理

说白了,就是对每一个gap间隔的分组进行插入排序,在将每个相同颜色的子序列都排成有序,就是这一趟的目的。

在这里插入图片描述
这就是第一趟全部跑完后的结果,所有颜色的子序列已经全部有序

第二趟
gap = 2
在这里插入图片描述
还是上面的思路,每一种颜色对应的子序列排序。

排完后
在这里插入图片描述
最后一趟 gap = 1
没有间隔,对所有数据进行插入排序,因为数据已基本有序,所以这一趟会特别快

在这里插入图片描述
排序结果
在这里插入图片描述
下面来进行代码实现

void ShellSort(int* arr, int n)
{
	int gap = n, i;
	//当gap = 1时说明只剩下一组,并且最后一组已排序,数组排序结束
	while(gap > 1)
	{
		gap = gap / 3 + 1;
		//每趟减少gap
		for(i = 0; i < n - gap; ++i)
		{
			int end = i;
			int temp = arr[end + gap];	
			
			while(end >= 0)
			{
				if(arr[end] > temp)
				{	
					arr[end + gap] = arr[end];			
					end -= gap;
				}
				else
					break;	
			} 
			arr[end + gap] = temp;
		}		
	}
}

上面的代码和直接插入排序极为相似,因为底层的核心思想还是直接插入排序,只需要将原来的步长1改为gap即可实现,当外层gap=1时说明内层gap为1的分组已经排序结束,数组已有序。

全部排序
在这里插入图片描述
希尔排序
时间复杂度:平均情况:O(n) 最好情况O(n^1.3) 最坏情况O(n^2)
空间复杂度:O(1)

发布了60 篇原创文章 · 获赞 78 · 访问量 6322

猜你喜欢

转载自blog.csdn.net/qq_35423154/article/details/105009249