排序算法之归并排序

归并排序是一种基于分治策略的排序算法,其基本思想是将数组分成两半,分别对每一半进行排序,然后合并两个已排序的子数组。文章详细介绍了递归和非递归两种实现方式,并强调了归并排序的时间复杂度为O(N*logN),空间复杂度为O(N),在稳定性上具有优势。
摘要由CSDN通过智能技术生成

排序算法之归并排序



一、归并排序基本思想

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

在这里插入图片描述
在这里插入图片描述

归并排序的思想就类似于无数次进行将两个有序的数组合并,而关键的问题是如何得到有序数组
将整个数组进行二分,分到最后,一组只会有一个元素,此时 数组就是有序的
整个分解和合并过程就是归并思想,这个过程近似于二叉树的后序遍历,先递归到最后得到有序数组,之后回到上层递归完成合并,一层一层直到回到最开始的数组左右合并,完成排序

二、递归实现归并排序

递归的实现过程可以观察上面第一张图
合并需要利用一个新数组,合并之后再拷贝回原数组
递归结束的条件是只有一个元素时已经有序,或者没有元素(此处是一个边界问题,写全面些不需要考虑,有兴趣可以研究下会不会出现没有元素的情况,就本人分析来应该是没有,如果不正确评论区留言)

// 归并排序递归实现
void _MergeSort(int* a, int* tmp, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}

	int midi = (begin + end) / 2;

	_MergeSort(a, tmp, begin, midi);
	_MergeSort(a, tmp, midi + 1, end);


	int begin1 = begin, begin2 = midi + 1;
	int end1 = midi, end2 = end;
	int i = begin;

	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, sizeof(int) * (end - begin + 1));
}



void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);

	if (tmp == NULL)
	{
		perror("malloc fail");
	}

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

	free(tmp);
}

三、非递归实现归并排序

非递归的过程可以看上面第二张动图
当gap=1时,每个元素都是单独的一个组,每个组都是有序的
当gap=2时,每两个元素是一个组,这时我们需要比较归并,使得组内有序
当gap=4时候,… …

我们在不用递归的时候依旧可以利用归并的思想,只不过与合并的顺序与递归方法有所不同,但大体上的实现途径是相同的
通过gap的大小来分每组的大小,从最开始的gap = 1 ,保证实现的每组都有序之后,以每次 gap *=2开始增加,不断合并保证每组都是有序的,直到gap > n结束

在讲完思路之后,我们还需要考虑特殊情况——边界问题
在这里插入图片描述
根据上图中,我们在实现的时候可能会碰见上面三种情况,如果没处理就会越界
所以当出现以上三种情况的时候,我们需要对其修正
先检查end2是否越界,一旦end2 > n-1 , 我们修正为 end2 = n-1
此时若begin2也越界,end2始终是小于begin2的,该部分不会进行合并
这时再检查 end1是否越界若end1越界则进行修正为 end1 = n - 1

// 归并排序非递归实现
void MergeSortNonR(int* a, int n)
{
	int gap = 1;
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
	}


	while (gap < n)
	{
		int i = 0;
		for (i = 0; i < n; i += gap * 2)
		{
			int begin1 = i, begin2 = i + gap;
			int end1 = i + gap - 1, end2 = i + gap * 2 - 1;
			int k = i;

			if (end2 > n - 1)
			{
				end2 = n - 1;
			}

			
			if (end1 > n - 1)
			{
				end1 = n - 1;
			}
			

			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])
				{
					tmp[k++] = a[begin1++];
				}
				else
				{
					tmp[k++] = a[begin2++];
				}
			}

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

			while (begin2 <= end2)
			{
				tmp[k++] = a[begin2++];
			}
		}
		memcpy(a, tmp, sizeof(int) * n);
		gap *= 2;
	}

	free(tmp);
}

四、归并排序的特性总结

  1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(N)
  4. 稳定性:稳定
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值