常见排序算法2

其他排序算法参见常见排序算法1
快速排序算法参见快速排序算法

一、直接插入排序

1、插入排序的基本思想

  • 把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为
    止,得到一个新的有序序列。
  • 直接插入排序就是一种简单的插入排序法,现实中,在我们玩扑克牌时,也用到了插入排序的思想。
    在这里插入图片描述

2、直接插入排序的基本思想

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

3、代码

void InsertSort(int* a, int n)
{
	assert(a);
	for (int i = 0; i < n-1; ++i)
	{
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0 && a[end] > tmp)
		{
			a[end + 1] = a[end];
			--end;
		}
		a[end + 1] = tmp;
	}
}

4、代码实现原理

  • 用两层循环套用的方式实现这一排序,第一层循环遍历前n - 2 个数,而序列的最后一个元素不需要遍历,因为在它前面的所有元素都已经有序了。
  • 用一个变量end代表已经排好的序列的元素中最后一个元素的位置,即尾标。
  • 用变量tmp保存要排序的数,因为等下该位置的值就会被前一个位置的值覆盖。
  • 接下来进入循环,只要前面的数(end所遍历的元素的值)大于欲排序的数(tmp),则前面的这些数都向后挪动一个位置。
  • 最后end + 1处的值修改为tmp,因为此时end所处位置的值一定小于或者等于tmp,或者是遍历完序列,即end为-1,tmp为序列的头元素赋值。
  • 至此第一趟循环结束,接着进行接下来的循环,而接下来的循环与第一趟循环类似。

5、动图展示代码实现的原理

在这里插入图片描述

6、直接插入排序的特性总结

  • 欲排序的元素集合越接近有序,直接插入排序算法的时间效率越高
  • 时间复杂度:O(N^2)
  • 空间复杂度:O(1)
  • 稳定性:稳定

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

1、基本思想

希尔排序在数组中采用跳跃式分组的策略,通过某个增量将数组元素划分为若干组,然后分组进行插入排序,随后逐步缩小增量,继续按组进行插入排序操作,直至增量为1。

2、代码

void ShellSort(int* a, int n)
{
	assert(a);

	int gap = n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;
		//gap = gap / 2;
		for (int i = 0; i < n - gap; ++i)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0 && a[end] > tmp)
			{
				a[end + gap] = a[end];
				end -= gap;
			}

			a[end + gap] = tmp;
		}
	}
}

3、代码实现原理

  • 创建一个变量gap,并赋值为n,gap表示每次比较的两个数的距离,用while循环对它进行判断,如果gap是1的话,结束循环。
  • 第一个while循环判断条件不能写成大于0,因为gap的值始终大于或者等于1,写成大于0的话会陷入死循环。
  • 因为while循环内部是先用gap再对gap进行判断,所以当gap等于1时,在while内部已经使用过gap(1)了,所以此时序列已经有序了。
  • 每次进入循环都对gap / 3 + 1(或者gap / 2),因为一开始gap是n,也就是说,从第一个数直接跳到序列末尾的下一位,即已经越界了,执行下面代码没有意义。
  • 又因为gap / 3 + 1最后一定会等于1,当gap等于1时,也就是直接插入排序,执行完序列已经有序了。而只有gap最后为1,才能保证最后有序,所以加1不能省略。
  • for循环里面只是把插入排序的1换成gap即可,但不是排序完一个分组,再去排序另一个分组,而是整体只遍历一遍,这样每次对于每组数据只排一部分,当整个循环结束之后,所有组的数据都排序完成。
  • 遍历是i 要小于 n - gap,因为当i等于n - gap时,对end加gap结果是n,而序列只有n - 1个元素,对它进行判断没有意义且是越界访问。
  • 其他的和直接插入排序差不多,这里不再过多说明。

4、gap遍历图

下图为gap = gap / 2的情况。
图中竖线所指的数中,从左边开始,两个两个一组进行比较,将小的数前移,大的数后移。

在这里插入图片描述

5、注意

  • 对gap赋值时不能用’n/3+1’,否则gap的值将永远是第一次的结果,会使循环陷入死循环。

6、希尔排序的特性总结

  • 希尔排序是对直接插入排序的优化。
  • 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序了,这时候排序就会变得很快。这样从整体而言才可以达到优化的效果。
  • 希尔排序的时间复杂度不好计算,因为gap的取值方法有很多,导致很难去计算。
  • 稳定性:不稳定。

三、 归并排序

1、基本思想

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and
Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有
序,再使子序列区间有序。若将两个有序表合并成一个有序表,则称之为二路归并。

2、核心步骤

在这里插入图片描述

3、代码

void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (begin >= end)
		return;

	int mid = (begin + end) / 2;

    // [begin, mid] [mid+1, end] 分治递归,让子区间有序
	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid+1, end, tmp);

	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin1;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			tmp[i++] = a[begin1++];
		}
		else
		{
			tmp[i++] = a[begin2++];
		}
	}

	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}

	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}

	memcpy(a + begin, tmp + begin, (end - begin + 1)*sizeof(int));
}

void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int)*n);
	if (tmp == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}

	_MergeSort(a, 0, n - 1, tmp);

	free(tmp);
}

4、代码实现原理

  • 首先,创建一块与序列一样大的空间,接着调用 _MergeSort 函数,对序列进行排序。
  • 当begin比end大或者相等时,证明该序列范围只有一个元素或者序列范围不存在,则直接返回不做任何处理。
  • 计算序列范围的中间位置,将序列范围分成两部分进行递归调用,直至序列范围只有一个元素或者没有。
  • 通过递归,此时序列范围有序或者只有一个元素,把序列分为了两个区间即[begin, mid] [mid+1, end],对它们的元素依次进行比较,比较小的先放入tmp空间中,当while循环结束时,说明有一个区间元素遍历完了,则判断两个区间哪个遍历完了就将另一个区间的数据直接存入tmp中。最后将归并数据(tmp中的数据)拷贝回原数组,方便下次排序。接着依次进行排序直至序列有序。
  • 最后将序列排序完返回到原函数后记得对开辟出来的空间进行释放以免造成内存泄漏。

5、动图展示代码实现的原理

在这里插入图片描述

6、注意

  • MergeSort 函数中,tmp的大小需要开辟够,否则会有越界的情况发生,并且在下方对tmp释放时也会报错。
  • 在MergeSort 函数中,对_MergeSort传参时,第三个参数大小需要减一,否则会有越界的情况发生。
  • _MergeSort函数的memcpy的参数如果是用数组形式,即头两个参数为 &a[begin], &tmp[begin] 时,不要忘了加上’&'。

7、归并排序的特性总结

  • 归并排序的缺点在于需要O(N)的空间复杂度,归并排序思考更多的是解决在磁盘中的外排序问题。
  • 时间复杂度:O(N*logN)
  • 空间复杂度:O(N)
  • 稳定性:稳定

本文到这里就结束了,如有错误或者不清楚的地方欢迎评论或者私信
创作不易,如果觉得写得不错,请务必一键三连💕💕💕

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值