11 插入排序和希尔排序

1. 插入排序

基本思想
直接插入排序是一种简单的插入排序法,基本思想:

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

在玩扑克牌时,就用了插入排序的思想

11 插入排序和希尔排序_第1张图片

过程

类似扑克牌,手里有3,7,8,9,,四张牌,这时,如果摸到4,怎么排它的位置。先和9比,小于9,再和前面的8比,再和7比,再和3比,这时大于3,就找到了它该插入的位置,将4放在3的后面,其他的往后挪一个

11 插入排序和希尔排序_第2张图片

从7开始排,第一个数肯定是有序的。
然后是4,4小于7,所以7应该在4的前面,将7往后挪一位,4放在7的位置
5比7小,7往后挪,5比4大,所以5的位置就是4的后面

这样不断比较,直到最后一个数排完

void Sort(int ary[], int len)
{
	//第一个是有序的,从第二个开始
	for (int i = 1; i < len; i++)
	{
		//[0,end]的区间是有序的,end是待排序数的前一个下标
		int end = i - 1;
		int temp = ary[i];
		while (end >= 0)
		{
			if (ary[end] > temp)
			{
				ary[end + 1] = ary[end];
				end--;
			}
			else
			{
				break;
			}
		}
		//此时,end是前一个位置
		ary[end + 1] = temp;
	}
	
}

特性总结:
1.元素集合越接近有序,直接插入排序算法的时间效率越高
2.时间复杂度:最坏O(N2),逆序的时候最坏,最好是O(N),正序的时候
3.空间复杂度: O(1)
4.稳定性:稳定

插入和冒泡
两者在最好和最坏的情况下时间复杂度相同,但对于大部分有序,局部无序的情况下,插入的适应性更强
11 插入排序和希尔排序_第3张图片

上面的数据只有最后面的9和8无序,对于冒泡排序来说,需要第一轮比过去,交换8和9,第二轮到9的位置后发现有序,排序完毕。插入排序来说,检查到8的时候,一交换,排序完毕。这时,插入排序的效率高。冒泡排序更容易理解,比较容易上手

2. 希尔排序

基本思想
希尔排序又称缩小增量法,基本思想是:先选定一个整数,把待排序文件中所有记录分成几个组,对每一组内的记录进行排序。然后缩小分组间隔。重复上述过程,当增量缩小到1时,所有记录排为一组,排好序

插入排序对大部分有序的数据效率很高,但对于倒序的效率很低。可以根据这个特性优化。先对数据用一种效率高的方法进行预排序,使数据接近有序。最后再进行一次插入排序,就可以排序成功

过程
11 插入排序和希尔排序_第4张图片
首先将整个数组按间隔gap分组,分为gap组,这里以间隔3分组

11 插入排序和希尔排序_第5张图片

上面的从9开始每间隔3分为归为一组,9,6,3,1分到了一组

然后从8开始分组,直到将所有数据分组完
11 插入排序和希尔排序_第6张图片
以颜色区分分为了3组,首先写一个类似的插入排序对红色组排序

void ShellSort(int ary[], int len)
{
//间隔3
	int gap = 3;
//0,从end+gap开始往前对比,到组内最后一个数排完,每次加间距
		for (int i = 0; i < len - gap; i += gap)
		{
			int end = i;
			int temp = ary[gap + end];
//将gap+end下标,也就是组内下一个数和前面的所有对比交换
			while (end >= 0)
			{
				if (ary[end] > temp)
				{
					ary[gap + end] = ary[end];
					end = end - gap;
				}
				else
				{
					break;
				}
			}

			ary[end + gap] = temp;
		}	
}

11 插入排序和希尔排序_第7张图片
将红色组排好了序,接着将其他两组也排好序,意味着需要循环组数的次数,控制变量i

	int gap = 3;
	//循环gap组
	for (int j = 0; j < gap; j++)
	{
		//从end+gap开始往前对比,找组内下一个数,每次加间距
		for (int i = j; i < len - gap; i += gap)
		{
			int end = i;
			int temp = ary[gap + end];
			//将gap+end下标,也就是组内下一个数和前面的所有对比交换
			while (end >= 0)
			{
				if (ary[end] > temp)
				{
					ary[gap + end] = ary[end];
					end = end - gap;
				}
				else
				{
					break;
				}
			}

			ary[end + gap] = temp;
		}
	}
	
}

11 插入排序和希尔排序_第8张图片

上面的数字仍是无序的,但经过预排序已经大部分有序

上面的三层循环可以减到2层循环,最外层的循环可以去掉。实际上外面两层循环本质上是把所有数据遍历了一遍,所以可以只用一层,多组并排,i每次递增1

void ShellSort(int ary[], int len)
{
	int gap = 3;

	//从end+gap开始往前对比,找组内下一个数,每次加间距
	for (int i = 0; i < len - gap; i++)
	{
		int end = i;
		int temp = ary[gap + end];
		//将gap+end下标,也就是组内下一个数和前面的所有对比交换
		while (end >= 0)
		{
			if (ary[end] > temp)
			{
				ary[gap + end] = ary[end];
				end = end - gap;
			}
			else
			{
				break;
			}
		}

		ary[end + gap] = temp;
	}
}

怎么让大部分有序的数据变为有序,只需要控制gap的变化,当gap=1时,就是插入排序。gap应该根据数据量变化,数据量大的时候,gap的跳跃变大,gap越大,越接近无序,但可以更快的将更大更小的数放在两端的位置

为了使排的数据最终变有序,gap的值最后必须可以变为1,官方给出的是gap每次/3+ 1,当gap为2时,就变为0,所以/3后再加1,这样无论多少数据,最终都会分为一组

void ShellSort(int ary[], int len)
{
	int gap = len;
	//gap>1预排序
	//gap=1插入排序
	while (gap > 1)
	{
		//+1保证最后一次是1
		//gap = gap/2
		gap = gap / 3 + 1;
		//从end+gap开始往前对比,找组内下一个数,每次加间距
		for (int i = 0; i < len - gap; i++)
		{
			int end = i;
			int temp = ary[gap + end];
			//将gap+end下标,也就是组内下一个数和前面的所有对比交换
			while (end >= 0)
			{
				if (ary[end] > temp)
				{
					ary[gap + end] = ary[end];
					end = end - gap;
				}
				else
				{
					break;
				}
			}

			ary[end + gap] = temp;
		}
	}
	
}

时间复杂度
11 插入排序和希尔排序_第9张图片
gap/3这一层时间复杂度log3N,因为每次都除以3。而对于里面的两个循环,时间复杂度计算比较麻烦,大致可认为是O(N)。因为gap每次都是变化的,且前面的排序对后面排序的增益效果无法估算
11 插入排序和希尔排序_第10张图片
上面只能以最坏的情况计算,但gap越小,情况应该是越好的

特性
1.希尔排序是对直接插入排序的优化
2.当gap>1时是预排序,目的是让数组更接近有序。gap==1时,数组已经接近有序了,就会很快
3.时间复杂度: O(N1.3)
在这里插入图片描述

11 插入排序和希尔排序_第11张图片

3.稳定性: 不稳定

3. 插入排序和希尔排序比较

随机生成10万个数字,计算两个排序所消耗的时间,大致差了100倍
11 插入排序和希尔排序_第12张图片

你可能感兴趣的:(排序算法,算法,数据结构,c语言)