插入排序和希尔排序

目录

前言:

一、插入排序

插入排序的单趟思想:

插入排序的完整思想:

插入排序代码:

对于插入排序的评价:

二、希尔排序

希尔排序的思想:

希尔排序代码:

 希尔排序的评价:

希尔排序的效率提升比较图


前言:

        相信各位看官老爷学了一段时间的代码也清楚排序对于我们的意义有多重要了吧,所以今天我会给大家带来2种有效的排序手段,插入排序和希尔排序,相信大家看了之后会对二者有更加深刻的理解。


一、插入排序

插入排序的单趟思想:

        插入排序就是建立在该插入数据的下标之前的的数组段是有序的,然后通过将该插入数据与前方的数据逐一比较,找到,对应的位置。可能大伙看着文字描述挺迷糊,那么请看下图:

         从图中,我们可以看到我们希望建立一个升序序列数组,在5这个数字之前,整个数组都是有序的,但是我们又需要将5插入进去,对于我们来说,可以将5直接插入在4的后面,但对于计算机来说,它是不知道5应该插在哪里的,所以有一个方法,向前依次比较,当插入的数据大于了比较数据,那么就表示已经找到了正确的位置。

        我们将5单独提取出来,与前方数据进行比较,如果小于前方数字,那么前方数字向后挪动,此时,原来的9的位置就已经空出来了,5就到了原来9的位子上去,此时因为比较还未结束,所以不用着急赋值,等到5找到了正确位置赋值也不迟。

        按照上图逐一比较,最终会得到下图内容:

         我们通过逐一比较,最终确定了5的正确位置,此时就可以终止比较,将5赋值到4的后面。

插入排序的完整思想:

        大伙看了插入排序的单趟思想,可能有一个疑问,那就是,我怎么保证插入数据之前的数据是一个有序序列呢,单纯只能排这么特殊的序列有点太没用了吧。

        那么我就在这里做出解释,我们不能保证前面的序列有序,但如果前面的序列只有一个数据呢?这时,它不就有序了?然后第一次排序完成,不就保证了前面两个长度的序列是有序了吗,然后反复进行该操作,直到最后一个数比较完成。

插入排序代码:

        相信大家结合图文加上我上面的讲解是能够理解的,这里我就不再讲解代码是如何实现的了,因为本身比较简单,重点是对于逻辑的理解。

//插入排序
void InsertSort(int* arr, int size)
{
	//i实际用于表示被比较数下标,而被比较数下标不能等于最后一位数据下标,只有插入数才能表示
	//而插入数又是被比较数的下标加一的值
	for (int i = 0; i < size - 1; i++)
	{
		//被比较数下标
		int Cmp_Index = i;

		//插入数为被比较数的后一个
		int Insert_Num = arr[Cmp_Index + 1];

		//Cmp_Index的有效值为0 -- size-1,
		while (Cmp_Index >= 0)
		{
			//当被比较数大于插入数据,被比较数向后挪动
			if (arr[Cmp_Index] > Insert_Num)
			{
				//挪动之后,被比较数--,向后移动,为下一次比较做准备
				arr[Cmp_Index + 1] = arr[Cmp_Index];
				Cmp_Index--;
			}

			//当被比较数小于或者等于插入数,表示找到了插入位置,所以跳出循环
			else
				break;
		}

		//因为被比较数更小,所以插入数需要放在被比较数的后一位
		arr[Cmp_Index + 1] = Insert_Num;

	}
}

对于插入排序的评价:

        我们能从插入排序的逻辑中得知,插入排序对于越是有序的情况下,它的向前比较次数就会越小,比如[ 1, 2, 3, 4, 5, 6, 7, 8, 9 ],这样的序列它只需要从前往后走一遍即可,时间复杂度为O(n),但是如果对于一个逆序数列来说,它的每一个数都要比较他前面的数组长度次,也就是一个等差数列,通过粗略计算能够知道它的时间复杂度为O(n^2)。由此可见,插入排序适用于一个比较有序数据情况下的排序手段。

二、希尔排序

希尔排序的思想:

        希尔排序其实就是插入排序的升级算法,从插入排序的评价中我们可以知道,当一个数据序列越是有序,那么它排序所需要消耗的时间就越少,所以,希尔排序就是在插入排序之前进行了一个预排续的作用。

        我们知道,插入排序的最差情况是因为序列如果为逆序,那么比较的效率就会很低,那么效率低的原因是什么呢?其实就是插入排序每次仅仅只往前比较了1长度。那么我们就可以在这个地方给他进行优化,也就是预排续操作。

        所谓预排续,也就是我们需要将一个完整的大组分为几个个小组比如一串逆序数组[ 9, 8, 7, 6, 5, 4, 3, 2, 1 ],我们可以将其分为3个为一组,也就是[ 9, 6, 3 ]、[ 8, 5, 2 ]、[ 7, 4, 1 ],这样做我们比较的单次比较的基数就会变小,比较的效率就会变高,当然,这里的分组只是逻辑上的分组,实际操作还是处于原本的数组上的。上述三个小数组比较完成之后,我们的大数组的数据就会变成为[ 3   2   1   6   5   4   9   8   7 ],大伙看,进行了预排续之后,整个数组数据相较于最开始,是不是就变得相对有序了呢,此时在进行步长为1插入排序,效率就会变得高起来。

        单次排序之后的结果如下图:

         插入排序的步长基于数据的长度而变化,因为要做到逐渐有序,最后有序的效果,这就表示了整个排序过程当中,步长会慢慢变小,直到为1,成为插入排序,成为有序数组。

希尔排序代码:

        希尔排序的内核还是和原来的插入排序一样,但是里面更改了一个东西,那就是每一个插入的数据都是与前面的n*gap长度的数据进行比较,当此次gap长度的预排序比较完成之后,就将gap除以2,进行下一次比较。因为除法是在while(gap>1)的循环内部进行,所以当gap被除为1的时候也就成为了插入排序,因为每一次的间距为1格。

//希尔排序
void ShellSort(int* arr, int size)
{
	//步长的决定通过原数据的个数来获取
	int gap = size;

	//当数据只有一个就不用进入比较,而数据个数为2,表示需要比较
	while (gap > 1)
	{
		//进入循环先将步长/2,否则直接用数据长度的预排没用
		//并且,每次/2可以让希尔排序越来越有序,直到为1,变为插入排序
		//变为插入排序之后,下一次就不会进入循环了

		gap /= 2;

		//本来i的跨度应为gap长度,但是我将其与另一个循环融合了起来
		//本来应该有一个j<gap;j++的循环比较,用于不同组的数据单独插入
		//但是融合之后,就变为了不同组的数据同时排序

		for (int i = 0; i < size - gap; i++)
		{
			//此后就是插入排序逻辑,不过将每一次的移动变为了gap
			int Cmp_Index = i;
			int Insert_Num = arr[Cmp_Index + gap];
			while (Cmp_Index >= 0)
			{
				if (arr[Cmp_Index]>Insert_Num)
				{
					arr[Cmp_Index + gap] = arr[Cmp_Index];
					Cmp_Index -= gap;
				}
				else
					break;
			}
			arr[Cmp_Index + gap] = Insert_Num;
		}
	}
}

 希尔排序的评价:

        希尔排序本身逻辑就是对于插入排序的升级,但是提升的效率是十分高的,虽然我没有办法计算希尔排序的时间复杂度,但是我通过查阅资料,知道了希尔排序的时间复杂度大概为O(n^1.3),不能计算的原因是因为我没有办法控制每一次预排续之后,下一次的变化,总而言之,希尔排序的效率极高,可以和我之后讲的大哥排序们相提并论,所以大伙需要掌握住。

希尔排序的效率提升比较图:

        从下图可以看到,插入排序排列一个逆序的100000数字时消耗的时间为9877毫秒,但是希尔排序只消耗了7毫秒,伙伴们感受到了差距了吗。

         以上就是我对希尔排序和插入排序的讲解,之后我讲解其它的排序手段。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值