插入排序的种类及实现

一、直接插入排序

基本思想是从第一个数开始,从后面每插入一个数就与前面的已有的数进行比较,一般情况下从小到大排序。

Code1

#include <stdio.h>
#include <stdlib.h>
#define T int
#define MAX_SIZE 20
typedef T Sqlist[MAX_SIZE];

void InsertSort1(Sqlist &L, int n)
{
	int i, j, temp;
	for(i= 1; i < n; ++i)
	{
		if (L[i] < L[i - 1])//若当前的数L[i]小于前驱
		{
			temp = L[i];//用temp暂存L[i]
			for (j = i - 1; j >= 0 && L[j] > temp; --j)//检查前面已经排好序的元素
				L[j + 1] = L[j]; //若前驱比这个数大,逻辑上前驱后移一位,内存里体现是前一个覆盖后一个。等同于L[i] = L[j];
			L[j + 1] = temp;
		}
	}
}

void main() {
	Sqlist L = { 0,49,38,65,97,76,13,27 };//第一个数字0不做排序处理
	int n = 8;
	for (int i = 1; i < n; i++)
		printf("%d ", L[i]);
		
	InsertSort1(L, n);
	
	printf("\n排序之后\n");
	for (int i = 1; i < n; i++)
		printf("%d ", L[i]);
}

模拟代码过程:第一个for循环开始,第一个数字49,i=1,不满足if条件。
第二个数字38,i=2,满足if(38<49),temp赋值当前数38,进入第二个for循环:
for(j=1; j>0&& L[j]>temp) L[2]=L[1]=49 ; 之后j–,跳出for循环后j=0,L[0]=temp=38,完成38的插入。
第 三个 数字65,i=3,不满足if条件。
第四个数字97,i=4,不满足if条件。
第五个数字76,i=5,满足if(76<97),temp赋值当前数76,进入第二个for循环:
j从当前数的前驱开始(即97),依次向前走, for(j=4; j>0&& L[j]>temp) L[5]=L[4]=76
不满足 for(j=3; j>0&& L[j]>temp)跳出循环,此时j=3, 所以L[4]=temp=76,完成76的插入。
到此,内存的数字排列应是0,38,49,65,76,97,13,27。
13和27插入过程也是如此,这里就不做分析了。

Code2

下面的代码也是直接插入排序,只是包装了一个swap交换函数,看起来就显得比较简洁,而且用不到第一位L[0]=0,,其实本质还是一样。

void Swap(T *a, T *b)
{
	T tmp = *a;
	*a = *b;
	*b = tmp;
}
void InsertSort(Sqlist &L, int n)
{
	for(int i=1; i<n; ++i)
	{
		if(L[i] < L[i-1])
		{
			Swap(&L[i],&L[i-1]);
			for(int j=i-1; j>0&&L[j]<L[j-1]; --j)
			{
				Swap(&L[j],&L[j-1]);
			}
		}
	}
}

前插存在的问题:如果是存在一组数38,49,再插入一个65,65要先插入到38的后面,再与49比较插入到49的后面,这就增加了比较和移动的次数。

1.2 改进的直接插入排序(第一位设为哨兵位)

基本思想是:把数组的第一位L[0](也就是新插入元素)当做哨兵,当新插入元素小于它的前一位元素时,用作新插入元素与前面其他已经排好序的元素之间做比较;当新插入元素大于它的前一位元素,不做比较。
如何进行比较并移动位置呢?
让L[0](新插入元素都会赋值给L[0])依次与新插入元素的前一位元素 (L[j]) 进行比较,L[0]<L[j],说明新插入元素要放到L[j]的前面,这时要让L[j]依次赋值给他的后一位L[j+1],从而找到新插入元素在数组中的位置,最后让L[0]赋值到那个位置即可。
在这里插入图片描述
改进的直接插入排序与上面的Code1代码类似,只不过上面code1中用了一个temp变量。

void InsertSort(Sqlist &L, int n)
{
	int i,j;
	for (i=2;i<=n;i++)
	{
		if (L[i] < L[i - 1]) {
			L[0] = L[i];
			for (j = i - 1; L[0] < L[j]; --j)
			{
				L[j + 1] = L[j];
			}
			L[j + 1] = L[0];
		}
	}
}

二、折半插入排序

先用折半查找找到元素应该插入的位置,在移动元素。虽然减少了比较的次数,但移动元素的次数没变。

void BInsertSort(Sqlist &L, int n)
{
	int i, j, low, high, mid;
	for (i = 2; i < n; ++i)
	{
		L[0] = L[i];//将当前要插入的数暂存到L[0]辅助空间
		low = 1; high = i - 1;//设置折半查找的范围
		while (low <= high)
		{
			int mid = (low + high) / 2;   //找到中间的那个数与将要插入的数比较,缩小范围
			if (L[0] >= L[mid])
				low = mid + 1;//查找右半子表,相等时为了保证稳定性要在右边插入
			else
				high = mid - 1;//查找左半子表
		}

		for (int j = i; j > high + 1; --j)//找到插入的位置,后面的数后移一位
		{
			L[j] = L[j - 1];//后移元素,空出插入位置
		}
		L[high + 1] = L[0];//将当前数插入
	}
}

模拟代码过程如下: 元素是49,38,65,97,76,13,27
i=2时,L[0]=L[i]=38,low=high=1,可以进入while,
第1次while,mid=1,if (L[0] >= L[mid])不成立,high=1-1=0查找左边部分
第2次while,while(low<=high)不成立,退出while,接下来就是移动元素了。

i=3时,L[0]=L[i]=65,low=1,high=2,可以进入while,
第1次while,mid=1,if (L[0] >= L[mid])成立,low=1+1=2
第2次while,mid=2,if (L[0] >= L[mid])成立,low=2+1=3
第2次while,while(low<=high)不成立,退出while,但不满足for循环,故无需移动,赋值即可。

三、希尔排序

它的工作原理是比较相隔一定距离的元素,并且每趟比较所用的距离随算法进行而减小,因此,希尔排序也叫做缩减增量排序。对于大规模的乱序数组插入排序很慢,因为它只会交换相邻元素,因此只能一点点地挪到相应位置,遇到极端情况如最小元素刚好在数组的最右端,那它要经过n-1次移动才能到正确位置,希尔排序为了加快速度,采用交换不相邻元素的方法对数组的一部分进行排序,先令不同间隔的数组变成有序状态,最后再用一次直接插入排序完成排序。

void ShellInsert(SqList &L, int n, int dk)//dk代表增量
{
	for(int i=dk+1; i<n; ++i)
	{
		if(L[i] < L[i-dk])
		{
			L[0] = L[i];
			for(int j=i-dk; j>0&&L[0]<L[j]; j-=dk)
			{
				L[j+dk] = L[j];
			}
			L[j+dk] = L[0];
		}
	}
}

void ShellSort(SqList &L, int n, int dlta[], int t)
{
	for(int k=0; k<t; ++k)
	{
		ShellInsert(L, n, dlta[k]);
	}
}

其他的插入排序还有2路-插入排序、表排序,但都不常用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值