排序(1)——直接插入排序、希尔排序

直接插入排序

直接插入排序是一种简单的插入排序法,其基本思想是:
把待排序的数据按顺序逐个插入到一个已经排好序的有序序列中,直到所有的数据插入完为止,得到一个新的有序序列。

当我们要插入第i个数据a[i]时,前面的a[0],a[1],…,a[i-1]已经排好序,此时用a[i]与前面的数据比较,找到一个合适的位置把a[i]插入,原来位置上的数据顺序后移。

例如,我们要对下面的一组数据进行插入排序(以升序为例)
在这里插入图片描述
第一趟排序:把第一个数据9看成是有序的,将6和9进行比较,9比6大,说明不符合升序,此时要把9向后移,然后把6插入到9原来的位置。
在这里插入图片描述
第二趟排序:经过第一趟排序之后,6和9已经有序了,此时要把8插入到合适的位置。先将8与9比较,9比8大,不符合升序,此时9向后移一个位置,再把8和6进行比较,符合升序,将8插入到原来9的位置。
在这里插入图片描述
第三趟排序:此时6,8,9已经有序,要把5插入到合适的位置,把5依次与6,8,9比较,6,8,9都比5大,不符合升序,6,8,9依次后移一个位置,把5插入到原来6的位置。
在这里插入图片描述
第四趟排序:此时7前面的数据已经有序,要把7插入到合适的位置。先将7与9比较,不符合升序,9后移一个位置;再把7与8比较,不符合升序,8后移一个位置;然后把7和6比较,符合升序,接着把7插入到原来8的位置。
在这里插入图片描述
此时数组已全部有序,插入排序结束。

插入排序的代码实现:

void InsertSort(int* a, int n)
{
	for (int i = 1; i < n; i++)
	{
		int tmp = a[i];//tmp保存要插入的数据
		int end = i - 1;//end是有序区间的结束位置,[0,end]为有序区间
		while (end >= 0)
		{
			if (a[end] > tmp)
			{
				a[end + 1] = a[end];//数据后移
				end--;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;//插入数据
	}
}

希尔排序(缩小增量排序)

希尔排序法又称缩小增量法,希尔排序是对直接插入排序的优化。
希尔排序的基本思想:
每一趟排序都选定一个gap,对间隔为gap的每一组分别进行直接插入排序。每一趟排序完成后,数组都更接近有序。当进行到最后一趟排序时,gap=1,当最后一趟排序完成后,数组已经有序。

当gap > 1时都是预排序,预排序的目的是让数组更接近有序,当gap = 1时,就是直接插入排序。

例如,我们现在要用希尔排序把下面的数组排成有序(以升序为例)。
在这里插入图片描述
我们先假设gap=3,现在对上面的数据进行分组,间隔为gap的为一组。
在这里插入图片描述
分组如上图所示,同颜色指向的为一组。
现在我们先对红色这组进行直接插入排序,代码如下:

void ShellSort(int* a, int n)
{
	int gap = 3;
	for (int i = 0; i < n - gap; i += gap)
	{
		int tmp = a[i + gap];
		int end = i;
		while (end >= 0)
		{
			if (a[end] > tmp)
			{
				a[end + gap] = a[end];
				end -= gap;
			}
			else
			{
				break;
			}
		}
		a[end + gap] = tmp;
	}
}

红色这组数据的第一趟插入排序:此时要把5插入到有序序列9之中。根据代码,此时iend是9的下标位置,指向9,tmp是5。end等于0,满足while循环条件,进入循环,a[end]是9,tmp是5,if条件满足,把9移动到5的位置,然后end减去gap。再判断while循环条件,此时end小于0,不满足,while循环结束,来到代码a[end + gap] = tmp,这句代码将5放到原来9的位置。
在这里插入图片描述
红色这组数据的第二趟插入排序:此时要把8插入到有序序列5,9之中,根据代码,此时iend是9的下标位置,指向9,tmp是8。end等于3,满足while循环条件,进入循环。a[end]是9,tmp是8,if条件满足,把9移动到8的位置,然后end减去gap。再判断while循环条件,此时end等于0,满足while循环条件,进入循环。此时end指向5,5小于8,不进入if语句,break跳出循环,来到代码a[end + gap] = tmp,这句代码将8放到原来9的位置。
在这里插入图片描述
此时红色这组数据已经排序完成。接下来,我们还要继续对其他组进行直接插入排序。对其他组进行直接插入排序,只要再加一层for循环就可以了,就像下面这样:

void ShellSort(int* a, int n)
{
	int gap = 3;
	for (int j = 0; j < gap; j++)
	{
		for (int i = 0; i < n - gap; i += gap)
		{
			int tmp = a[i + gap];
			int end = i;
			while (end >= 0)
			{
				if (a[end] > tmp)
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}

通过j来控制对哪一组进行直接插入排序,j = 0是红色这组,j = 1绿色这组,j = 2是蓝色这组。

接下来我们还要解决一个问题,当只有10个左右数据的时候,gap取3或者取其他相近的值是可以的。但当数据量非常大的时候,比如有一亿个数据,gap还取3就不合理了,那么gap如何取值呢?

当gap越大,跳得越快,越不接近有序;当gap越小,跳得越慢,越接近有序。

如何理解上面这句话呢?比方说我们有一个10个数的数组,最大的数在最前面,如果gap取3,最大的数跳3次就可以跳到最后一个位置,而如果gap=1,即是直接插入排序的时候,最大的数要跳10次才能跳到最后。因此,gap取大一点,可以让较大的数更快跳到后面,让较小的数更快跳到前面。但是gap一直取大,就越不接近有序,因此在实际情况中,gap是变化的。gap的取值一般有两种,一种是gap = n/2,一种是gap = n/3 + 1。通过gap由大变小的方式,可以让数据跳得更快,并且慢慢接近有序,最后gap=1,就是直接插入排序,保证数组是有序的。

再加上一层循环,我们的希尔排序就完成了。希尔排序的最终代码:

void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap >= 1)
	{
		gap = gap / 2;
		for (int j = 0; j < gap; j++)
		{
			for (int i = j; i < n - gap; i += gap)
			{
				int tmp = a[i + gap];
				int end = i;
				while (end >= 0)
				{
					if (a[end] > tmp)
					{
						a[end + gap] = a[end];
						end -= gap;
					}
					else
					{
						break;
					}
				}
				a[end + gap] = tmp;
			}
		}
	}
}

示例数组的完整希尔排序过程:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值