插入排序:直接插入、交换插入、折半插入

插入排序把一个数插入到一个有序的序列中,并要求插入后此数据序列仍然有序。这种排序思想就是插入排序。

    那么对于一个原始无序的序列,哪里找有序的部分呢?这个很简单,可以把序列分为两个部分,第一个元素是第一部分,其余元素是第二部分。第一个部分只有一个元素,当然是有序的!对于如何把其余元素插入到第一部分中去,按插入策略,可分为如下几种插入方法:


直接插入:把余下元素一个一个插入有序表中,每次都从有序表的最后一个元素开始比较,若是小于该元素:data<a[i],则再与前一个元素比较data[i-1],...直到找到合适位置,最后把该位置及其以后的元素都向后移动:a[j]=a[j-1],腾出一个位置让新元素插入。这也是最简单的插入排序算法。

看代码:

void InsertSort(int *a, int n)  //直接插入排序 
{
	if(a==NULL || n<=1)  //assert(a && n>0);
	return;
	for(int i=1; i<n; i++)
	{
		int j=i; 
		int temp=a[i];
		while(j && temp<a[j-1])
		{
			a[j]=a[j-1];
			j--;
		}
		a[j]=temp;
	}	
}


交换插入:也可以在比较的过程中,直接交换前后位置的元素,一步一步地把插入的元素交换到合适的位置,如下:

void InsertSort(int *a, int n)  //直接插入排序 
{
	if(a==NULL || n<=1)  //assert(a && n>0);
	return;
	for(int i=1; i<n; i++)
	{
		int j=i; 
		while(j && a[j]<a[j-1])
		{
			swap(a[j],a[j-1]);   //交换元素 
			j--;
		}
	}	
}


折半插入:在直接插入中,我们每次都是把一个元素插入到一个有序的序列中。为了使找到位置更高效,我们可以借鉴二分查找的方法,减少比较次数,快速找到合适的插入位置。这就是折半插入的来历。

根据二分查找时,区间选取的不同,可细分为如下两种:注意比较两者的细微差别哦

代码一:左闭右闭 [low,high]

void BInsertSort(int *a, int n)  //Binary Insert Sort  折半插入排序 
{
	assert(a && n>0);
	for(int i=1; i<n; ++i)
	{
		int low,high;
		low=0;
		high=i-1;
		int mid;
		while(low<=high)  //使用二分查找,寻找插入的位置 
		{
			mid=low+((high-low)>>1);    //这种写法,有效避免溢出
			if(a[i]>a[mid])
			low=mid+1;
			else
			high=mid-1;
		}
		
		int temp=a[i];
		for(int j=i; j>low; j--)  //移动元素 
		a[j]=a[j-1];

		a[low]=temp;  //在合适位置,插入。这里为什么是 low? 得仔细想想!  
	}
}

代码二:左闭右开 [low,high)

void BInsertSort(int *a, int n)  //Binary Insert Sort  折半插入排序 
{
	assert(a && n>0);
	for(int i=1; i<n; ++i)
	{
		int low,high;
		low=0;
		high=i;    //这里的写法,要注意 
		int mid;
		while(low<high)  //使用二分查找,寻找插入的位置 
		{
			mid=low+((high-low)>>1);
			if(a[i]>a[mid])
			low=mid+1;
			else
			high=mid;
		}
		
		int temp=a[i];
		for(int j=i; j>low; j--)  //移动元素 
		a[j]=a[j-1];

		a[low]=temp;  //在合适位置,插入 
	}
}


update:2014-5-31 16:33

写完这篇博客后,发现很多人对二分插入,最后应当插入的位置不是很明白,为什么代码一中最后新元素的插入位置是a[low]=temp呢?下面教你透彻了解二分插入的前前后后。

测试代码:

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
void printArray(int a[], int n)   //打印数组 
{
	for(int i=0; i<n; i++)
	printf("%-4d",a[i]);
	printf("\n");
}
void BInsertSort(int a[], int n)   //折半插入 
{
	for(int i=1; i<n; i++)
	{
		int low,high;
		low=0,high=i-1;
		int mid;
		printf("i=%d\n",i);
		printArray(a,n);
		while(low<=high)
		{
			mid=low+((high-low)>>1);
			printf("low=%d,high=%d,mid=%d\n",low,high,mid);
			if(a[i]<a[mid])
			high=mid-1;
			else 
			low=mid+1;
			printf("low=%d,high=%d,mid=%d\n",low,high,mid);
		}
		int temp=a[i];
		for(int j=i; j>low; j--)
		a[j]=a[j-1];
		a[low]=temp;   //这里如写high+1,则上面的j>low,应改为j>high+1,原因见下面的分析
	}
} 
int main()
{
 	const int N=6;
 	srand((unsigned)time(NULL));
 	int a[N];
 	for(int i=0; i<N; i++)   //打印下标 
 	printf("%-4d",i);
	printf("\n");
 	for(int i=0; i<N; i++)
 	a[i]=rand()%100;
 	BInsertSort(a,N);
 	printArray(a,N);
	system("pause");
 	return 0;
}
其中的一次运行结果是:

0    1    2    3    4    5       //下标

i=1                                  //插入下标为i的数,下同

83  19  83  1    8    38

low=0,high=0,mid=0

low=0,high=-1,mid=0

i=2

19  83  83  1    8    38    //上一次插入后的结果,19插入了0号位置,上一轮low=0

low=0,high=1,mid=0

low=1,high=1,mid=0

low=1,high=1,mid=1

low=2,high=1,mid=1

i=3

19  83  83  1    8    38    //83插入2号位值,其实未移动,上一轮low=2

low=0,high=2,mid=1

low=0,high=0,mid=1

low=0,high=0,mid=0

low=0,high=-1,mid=0

i=4

1  19  83  83    8    38    //1插入了0号位置,上一轮low=0

low=0,high=3,mid=1

low=0,high=0,mid=1

low=0,high=0,mid=0

low=1,high=0,mid=0

i=5

1   8   19  83   83    38    //8插入了1号位置,上一轮low=1

low=0,high=4,mid=2

low=3,high=4,mid=2

low=3,high=4,mid=3

low=3,high=2,mid=3

1   8   19   38   83   83   //38插入了3号位置,上一轮low=3,至此排序结束

从代码中我们可以知道,low,high,mid的值是两个一组的,上一组是a[i]和a[mid]未经比较以前的,下一组是比较后,经过调整的。调整结果要么是low=mid+1,要么是high=mid-1。

不知大家从上面的数据看出了什么。对!我们现在可以清楚的肯定:新元素的插入位置就是low。并且还发现high比low要小一,即high+1才与low相等。这是显然的,否则while循环如何结束。

我想此刻大家的疑惑肯定是解开了。那么插入位置的写法就很随意了:low和high+1都行!

用同样的方法对代码二这种左闭右开的区间经行测试,会发现:插入位置仍然是low,最后while循环终止的条件是low和high相等。


以上代码,大家最好动手测试一下。有问题,及时通知我修改哦!


转载请注明出处,本文地址:http://blog.csdn.net/zhangxiangdavaid/article/details/27373183


若是对你有所帮助,或是觉得有意思,希望顶一个哦。


专栏目录:



  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值