【C数据结构】插入排序和希尔排序

一、插入排序

1、基本思想

  插入排序的基本思想,把待排序的一个序列,按照一定的大小顺序,将一个个数插入到一个已经排好序的有序序列中。

假设有一组无序序列,我们需要排一个升序的序列
在这里插入图片描述
我们可以将第一个数,单独的看成是一个有序的序列。
在这里插入图片描述
然后把这个有序序列的右边一个值当作待插入数(如3),当3小于5就放在5的左边。
在这里插入图片描述
接下来让每个待插入的数,和已经有序的序列中的值进行比较,若小于其中一个值就放在左边,直到排成有序序列。

通过这个方法,我们可以很明显的看到它的时间复杂度是O(N^2),可见它的效率很低。

2、插入排序代码实现

首先我们需要一个变量,它能从有序序列最后一个值开始,方便和插入的值进行遍历比较。
在这里插入图片描述
并且每次需要插入比较的值,都是最开始end+1个位置的,而之后需要比较end–的位置会变化,所以可以通过一个tmp变量储存一开始待插入end+1的值,保存好待插入的值后,就可以对有序序列值的位置进行改变,再在最后将tmp插入到应该在的位置。

并且end的最大值应该是在最后一个数的前面一位,不然end+1会越界的。
在这里插入图片描述

//插入排序
void InsertSort(int* arr, int n)
{
	for (int i = 0; i < n - 1; i++)//end位置范围第1到第n-1个数。
	{
		int end=i; //记录end位置
		int tmp = arr[end + 1]; //记录待插入的数
		while (end >= 0)
		{
			if (arr[end] > tmp)
			{
				arr[end + 1] = arr[end];//直接覆盖上一个值。
				end--;
			}
			else
			{
				break;//当待插入的数大于或等于这个数就不需要往下了。
			}
		}

		arr[end + 1] = tmp;//最后再赋待插入的值

	}

	/*print(arr, n);*/
}

值得注意的是:
如果排顺序
插入排序当序列接近顺序或者顺序的时候,时间复杂度只有O(N),而当序列接近逆序或者逆序的时候,时间复杂度为O(N*N)

稳定性角度来看,直接插入排序中没有出现分组的情况,所有的数都在一个组里进行,相同的数之间顺序在有序后不会发生改变,所以稳定。


二、希尔排序

1、基本思想—预处理和插入排序

希尔排序在插入排序的基础上通过预处理,巧妙的大大提高了算法的效率。

假设需要对以下序列排升序。
在这里插入图片描述
现在对预处理中的一种情况进行处理:
希尔排序通过一个间隔(假设这种情况下间隔gap=3),从最开始的数开始依次将一个序列划分,一直到那个数的位置+3越界为止(如8的位置),并且也可以对划分进行分组(如5 2 1一组,7 3 0一组,4 8一组),每个数只会在一个组,所以间隔为1的时候就只有一个组

从头开始,分别对每组进行插入排序,比如第一组5 2 1,先将5看成一个有序序列,并且令其位置为end,再用一个变量储存end+gap位置的数,end=end-gap后,如果end小于0,end+gap位置就放入待插的数。
在这里插入图片描述
直到这一组排成有序为止。
在这里插入图片描述
但是希尔排序的算法,并不是通过一组一组的单独排序,而是通过从第一个数开始,依次进行排序。
在这里插入图片描述
现在让我们来看看,不同的间距(gap) 对一个序列有多大的影响。
  如果一个序列有n个数,那么最大间距就是n-1,而当间距为n-1的时候,无非就是第一个数和最后一个数进行排序。
  如果一个序列的间距为1,那么就是插入排序。
  所以从两种极端情况来看,当间距大的时候序列就越无序,当间距小的时候序列就越趋向有序

所以在预处理,我们需要先通过大的间隔进行排序,再通过小的间隔进行排序,而大的间隔排序时,整个排序次数较低,在接近有序的基础上,通过小的间隔排序,因为更趋近有序,所以排序的次数似乎也变得低了,下面通过代码看看。

2、代码实现

希尔排序是直接在插入排序的基础上进行优化。
1、先通过预处理,让序列接近有序
2、再直接进行插入排序

gap > 1都是对序列进行预处理,而当gap = 1的时候,由于这个时候序列已经接近有序,所以在直接插入排序效率就很高。

第一种写法:齐头并进

//希尔排序
void ShellSort(int* arr, int n)
{
	int gap = n;//因为数大、数小不确定,所以开始定义为n。
	while (gap > 1)
	{
		//gap = gap / 2;	//这也是一种取gap的方式
		gap = gap / 3 + 1;	//加1确保gap最后的间隔为1
		//序列n-gap-1是分组最后的位置,当gap为1就是插入排序
		for (int i = 0; i < n - gap; i++)
		{
			int end = i;
			int tmp = arr[end + gap];
			while (end >= 0)
			{
				if (arr[end] > tmp)
				{
					arr[end + gap] = arr[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			arr[end + gap] = tmp;
		}//end of for

	}//end of while

	/*print(arr, n);*/

}

另一种写法,它不同在于分组实现

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

				a[end + gap] = tmp;
			}
		}
		
	}

}

希尔排序的时间复杂度不好计算,第一点在于gap的取值方式有多种,第二点在插入排序的过程中因为gap的改变,导致序列的有序情况不同,也就不能用最坏的情况去衡量每一次的排序,也就难以计算。最后可以保守在O(n^1.3)。

稳定性,希尔排序中,因为gap导致分组的存在,相同的数在排序后可能先后顺序会不一样,所以不稳定。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值