三种插入排序算法解析

【前言】我们常见的排序主要分为两类,一类是内部排序,一类是外部排序

1.内部排序:数据元素放在内存中的排序

2.外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求能在内外村之间移动数据的排序

首先我们先来了解一下如何判断一个排序算法的性能好坏?依据是什么?

一般而言有三个依据:稳定性、时间复杂度和空间复杂度

1.稳定性

如果在元素序列中有两个元素A[i]和A[j],他们的排序码K[i]==k[j],并且在排序之前,元素A[i]在A[j]之前,排序之后,元素A[i]仍然在A[j]之前,则称这个排序算法是稳定的,否则是不稳定的(排序码:一般数据元素有多个属性域,其中一个属性域可用来区分元素,作为排序依据,该域即为排序码)

2.时间复杂度

时间复杂度其实就相当于一个函数,这个函数计算的是该算法执行基本操作的次数,我们一般用大O渐进法表示

一个算法中语句总的执行次数称为语句的频度或时间频度,记为T(n),n称为问题的规模,一般情况下,T(n)是n的某个函数f(n),当n不断变化时,T(n)也会随之变化,当n趋于无穷大时,算法执行次数的增长率和f(n)的增长率相同,记作T(n)=O(f(n)),称O(f(n))为该算法的时间复杂度

3.空间复杂度

和时间复杂度类似,也是使用大O渐进法,即就是求对象的个数

【注意】递归算法的空间复杂度=递归深度*每次递归的空间大小(如果内次递归的空间为常数,则空间复杂度为O(N))

下面我们来看看几种常见的排序方法~

我们今天先来看内部排序的一种---->插入排序

我们都玩过扑克牌吧,在玩的时候,我们摸一张牌,然后在手里找相应的位置插入,那么我们具体是怎么插入才保证手里的牌始终是按顺序排放着呢?

插入排序的思想就跟玩扑克一样,每一步将一个待排序的元素,按其排序码的大小,插入到前面已经排好序的一组元素的合适位置上去,直到元素全部插完为止

1、直接插入排序

【基本思想】

当插入第i(i>=1)个元素时,前面array[0],array[1],array[2]......array[i-1]个元素已经排好序了,此时array[i]的排序码与array[i-1],array[i-2]....的排序码进行比较,找到插入位置即将array[i]插入,原来位置上的元素向后顺序后移。

【图解】


【代码实现】

void InsertSort(int *array, size_t size)
{
	for (size_t i = 0; i < size; i++)//排序趟数
	{
		int key = array[i];//key保存当前要插入的元素,以防前面元素后移时把它覆盖
		int end = i - 1;
		while (end >= 0 && key < array[end])//当前元素与他之前的元素比大小,然后从后向前一次比较寻找插入位置
		{
			array[end + 1] = array[end];
			end--;
		}
		array[end + 1] = key;//插入元素
	}
}

【性能分析】

元素集合越接近于有序,直接插入排序算法的时间效率就越高,也就是说插入排序适用于处理数据量比较少或者部分有序的数据

(1)时间复杂度

对于n个数字,首先我从最外层的for循环要进行n次,然后里面的while循环是根据i决定的,i=0时,不进入循环;i=1时循环1次;i、=2时,循环2次;i=3时循环3次,.....i=n-1时循环n-1次,则加起来就是1+2+3+4+......+n-1=,根据大O渐进法复杂度计算规则,保留最高阶,并去掉系数,那么时间复杂度为(最坏情况),最好的情况是数组中各元素都已序,则时间复杂度为,那么平均情况下次数为,所以综上,直接排序的时间复杂度为

(2)空间复杂度

空间复杂度为O(1),原因只创建了一个对象key

(3)稳定性

是稳定的,因为在比较的时候,如果两个数相等的话(对比看上图中两个23),不会进行移动,前后两个数的次序不会发生改变

2、插入排序的优化版本--->折半插入排序

【基本思想】

折半插入排序又称二分法插入排序,基本思想是:设在数据表中有一个元素序列array[0],array[1]......array[n-1],其中array[0],array[1]....array[i-1]是已序序列,在插入array[i]时,采用二分查找寻找array[i]的插入位置

【图解】


【代码实现】

void InsertSort2(int *array, int size)
{
	for (int i = 1; i < size; i++)
	{
		//已序序列中找插入元素的位置
		int key = array[i];
		int left = 0;
		int right = i - 1;
		while (left <= right)//利用二分查找思想查找插入位置
		{
			int mid = left + ((right - left) >> 1);//取中点
			if (key < array[mid])//插入值小于中点值
			{
				right = mid - 1;//向左缩小区间
			}
			else
			{
				left = mid + 1;//否则,向右缩小区间
			}
		}//至此,找到插入位置
		int end = i - 1;
		while (end >= left)//搬移元素,成块移动,空出插入位置
		{
			array[end + 1] = array[end];
			end--;
		}
		array[left] = key;//插入元素
	}
}

【优化方面】

  折半查找比直接查找要快,在插入第i个元素时,需要经过次比较,才能确定插入位置,因此将n个元素()用折半插入排序的比较次数为,约等于,所以时间复杂度为O(nlgn),空间和和稳定性和直接插入排序一样

【分析】

与直接插入排序比较当n比较大时,总的比较次数比直接插入排序的最差情况好的多,但是比其最好的情况要差

所以,在元素的初始排列已经按排序码排好序或接近有序时,还是选择直接排序的好。

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

【基本思想】

设待排序元素序列有n个元素,首先取一个整数gap<n作为间隔,将全部元素分为gap个子序列,所有距离为gap的元素放在同一个子序列中,在每一个子序列中分别用直接插入排序,然后gap--或者gap=gap/2;重复上面动作,直到取到gap==1,将所有有元素放在同一个序列中排序为止

【图解】


【代码实现】

void ShellSort1(int *array, int size)
{
	int gap = 3;//也可以取素数//也可以gap/2
	while (gap)
	{
		for (size_t i = gap; i < size; i++)
		{
			int key = array[i];
			int end = i - gap;
			while (end >= 0 &&key < array[end])
			{
				array[end + gap] = array[end];
				end -= gap;
			}
			array[end + gap] = key;
		}
		gap--;
	}
}
//下面这种增量gap取的是 gap = gap / 3 + 1
void ShellSort2(int *array,int n)
{
	int gap = n;
	while (gap>1)
	{
		gap = gap / 3 + 1;
		for (int i = 0; i < n - gap; i++)
		{
			int end = i;
			int tmp = array[end + gap];
			while (end >= 0 && array[end]>tmp)
			{
				array[end + gap] = array[end];
				end -= gap;
			}
			array[end + gap] = tmp;
		}
	}
}

【优化方面】

(1)由于gap的取法很多,不同的取法也就造成了希尔排序算法的性能问题有很大的差距,有些序列的效率会明显增高,比如有序序列。所以对希尔排序的时间复杂度分析很困难(至少我分析不出来具体),但是大量对应统计指出,当n很大时,时间复杂度在之间

(2)对于大规模的序列(n<=1000),希尔排序都具有很高的效率,并且希尔排序算法的代码简单,容易执行,所以很多排序应用程序选用了希尔排序算法

(3)希尔排序是一种不稳定的算法




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值