插入排序(直接插入和希尔排序)

32 篇文章 0 订阅
2 篇文章 0 订阅

目录

写在前面的话

一,直接插入排序

1.1思路实现

一趟排序实现

整个排序实现

1.2代码实现 

1.3复杂度以及稳定性 

1.4可以改进的点

二,希尔排序

2.1希尔排序思路

第一步 分组预排序

第二步:直接插入

2.2代码实现

代码思路分析(重要)


写在前面的话

小伙伴们大家好啊!这里是小菜鸡森哩。今天森哩为大家带来的是有关排序的内容。那么首先我们来了解一下插入排序的有关内容,首先是直接插入以及他的优化,希尔排序。

                                                  

一,直接插入排序

好的,那么首先对于插入排序来说,(升序为例)其原理就是找出小的值从后面移动到前面。

1.1思路实现

一趟排序实现:

如下图所示,那么首先我们从数组头部开始。将第一个元素下标记为 end,然后将其后面那个位置元素记为 x。(这里以升序为例,但道理都是相同的

那么首先,先将end位置后面那个位置元素 x 记下来,然后end与其比较,如果end位置元素大,则将end位置元素后移到后面那个位置,然后end再往前移动一位,直到end位置所表示的元素属于空的时候,表示当前一趟排序完事。 

比如,当前位置移动一次后:

此时,当 end 位置已经不在数组中之后,则说明这一趟排序结束,则需要跳出循环。

当然,也有可能在排序途中元素 x 大于 end 位置元素,此时直接跳出循环,说明当前该小部分数组是有序的。比如下图所示:

最后,在一趟排序或者跳出循环之后,end 后面那个位置的元素一定是空的(或者没有移动时候的 x 元素),需要将记录的值 x 填入那个空位置(或者原先自己的位置)。

整个排序实现

那么一趟排序实现了,多次的话,我们就只需要控制 end 指针的位置,让其从数组首元素开始,一直移动到数组倒数第二个元素,不需要移动到最后一个元素。因为当 end 处于倒数第二个元素位置时,需要和后面位置元素进行排序。如果我们没有控制好循环的范围,那么函数将会出现错误。

1.2代码实现 

//直接插入排序
void InsertSort(int* a, int n)
{
	assert(a);
	for (int i = 0; i < n - 1; i++)
	{
		//一次while循环,一趟排序,将x之前的所有元素排成有序
		int end = i;
		int x = a[end + 1];
		while (end >= 0)
		{
			//如果end所在元素大于后面元素,则将其移动到end+1位置上,
            //然后再判断,同时为x元素腾出位置
			if (a[end] > x)
			{
				a[end + 1] = a[end];
				end--;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = x;
	}
}

1.3复杂度以及稳定性 

复杂度:

首先我们知道,如果有一串降序的数组,我们升序排序的话,也就是最坏的情况。此时每一趟排序都需要 end 都需要移动到小于 0 的位置。此时可以求出时间复杂度为 O(N^2)。

而因为直接插入排序只开辟了可计算的几个变量的空间,所以时间复杂度是O(1)。

稳定性:

因为直接插入只是将每个可能需要移动的元素,只往前或者往后移动,没有乱移动,也没有往前移动一次,下次又往后移动一次。所以说是稳定的。

1.4可以改进的点

如果当前数组是一组比较有序的数组,那么此时直接插入排序是很高效的,因为移动不是很大。所以接下来的希尔排序就是做了这样一个优化操作。

二,希尔排序

希尔排序是在直接插入排序的基础上改进的,因为直接插入排序的时间复杂度确实太高了。所以希尔排序是提出了一种改进方法,减少每一次排序的次数,将时间复杂度改进到O(N)。

那么我们来具体看一下希尔排序是怎样实现的。

2.1希尔排序思路

因为是对直接插入排序的优化,所以首先我们需要将其进行预排序,预排序之后,当前的序列是相对比较有序的,我们再进行直接插入,这样就达到了简化时间复杂度的操作。同时也就达到了对于比较有序的序列的排序。

第一步 分组预排序

如下图所示,我们首先假设 gap 为 3,意为着每两个数之间的距离是 3 ,然后我们将这个序列分为三组(分别为红线组,紫线组,绿线组),每一组是位置间隔为 3 的数,然后我们对着三组数进行直接插入排序排序。

然后,我们就得到了下面的新序列:

下面这个序列虽然不是完全有序的,但是相比于原来的数组而言,是更加有序了。如果此时我们再进行一次预排序,那么就会更加有序,直到gap为 1 ,虽然可能不是完全有序的。

第二步:直接插入

当 gap 为 1 时,也就相当于直接插入了,那么此时直接插入就可以非常高效将其排成有序序列了。所以最主要的是分组预排序。 

2.2代码实现

void ShellSort(int* a, int n)
{
	assert(a);
	//多次预排序(gap > 1) +直接插入 (gap == 1)
	int gap = n;
	//gap等于 1 的时候,说明是插入排序了
	while (gap > 1)
	{
		//研究表明,gap除 3 的时候,效果更好
		//但是有时候单单除以 3 的话,有可能导致gap取不到 1
		gap = gap / 3 + 1;
		//gap = gap / 2;
		// 多组一起排
		for (int i = 0; i < n - gap; ++i)
		{
			int end = i;
			int x = a[end + gap];
			while (end >= 0)
			{
				if (a[end] > x)
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = x;
		}
	}
}

代码思路分析(重要):

那么对于希尔排序代码,我们有以下几个方面的解释。

1. gap 值一般是会取 3 或者 2 ,但是研究表明取 3 的话,效率更高一点,但同时需要注意,如果只是对 3 取除数的话,可能取不到 1,所以在取 3 的除数的时候,需要加上 1 。

2.这里我们采用多组一起排的思路,也即不是平常的一组排完,再去排另一组,而是挨个数排,但是排的不是全部的数,而是分组的数进行排序,也就是我们不通过两个循环去实现,而是一个循环即实现每一组的排序,同时实现组间的移动。 

3.分组排序 end 指针每次移动 gap 个数。

2.3复杂度及稳定性

复杂度:

对于希尔排序来说,最主要的就是将整组数进行了分组预排序,这样使得我们当前数组变成了一个比较有序的数组,而且在这个过程中时间复杂度是远小于 O(N^2) 的。

而对于一个比较有序的数组来说,直接插入排序的效率是很高的,因为不需要移动很多次。这里我们求得希尔排序最后的时间复杂度约为 O(N^1.3),因为是一个范围值,所以我们取中间值。

 稳定性:

我们知道,分组预排序的时候,因为每一组之间的间距是比较大的,所以在移动时,每个元素的相对位置移动是比较灵活的。这也就导致了希尔排序的稳定性是比较差的。所以希尔排序是不稳定的。

好的,那么本文到此就结束啦!如果有问题的话,还请指正呀!如果觉得还不错的话,跪求三连呀!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值