数据结构·归并排序

一、核心思想

归并排序是一种建立在归并操作上的一种有效的排序算法,这是采用分治法的一个典型应用。想要得到完全有序的序列,就先使得每个子序列有序,再使得子区间段有序,最后整个序列有序。若将两个有序表合并成一个有序表,称为二路归并。归并排序的核心就是把数组拆分再一点点地合并,并在每次合并后时合并的这部分有序,直到合并成整个数组。
归并排序·分治

二、算法分析

归并排序·动图演示
归并排序算法并没有直接在原数组上执行,而是借助了一个malloc出来的临时数组。归并排序的核心在于将两个已排序的子数组合并成一个有序的数组。 合并过程中,我们需要 同时比较两个子数组的元素,并将它们按照大小顺序放入一个新的序列中。这个新的序列通常就是一个临时数组,它用于存储合并后的结果,并且在合并完成后会覆盖或者替换原来的子数组。
如果不使用临时数组,我们就没有一个独立的空间来存储合并后的结果。尝试直接在原数组上进行合并操作会导致数据覆盖和丢失的问题。具体来说,当我们从两个子数组中取出元素并放入原数组时,会破坏那些尚未参与合并的元素的顺序,使得整个排序过程变得混乱。
归并排序的递归性质也要求我们在每个递归层级上都有一个独立的临时空间来存储合并后的结果。如果没有这个临时空间,递归过程将无法进行。基于归并排序的原理和特性,我们必须使用临时数组来实现合并操作。当然,有些优化或变种的排序算法可能会尝试减少临时空间的使用,但它们通常会在算法的其他方面做出妥协,比如增加算法的复杂度或降低排序的稳定性等。
不使用临时数组是无法实现标准的归并排序算法的。临时数组在归并排序中起到了至关重要的作用,它保证了合并操作的正确性和高效性

1、递归版本

  1. 分解:
    归并排序开始于将待排序的数组不断地“一分为二”,直到每个子数组只包含一个元素。这个过程是递归进行的,即每个子数组也会继续被分解成更小的子数组,直到每个子数组只包含一个元素。
  2. 递归排序与合并:
    在分解过程完成后,递归地开始合并这些子数组。合并时,会取出两个相邻的子数组,并将它们合并成一个有序的新数组。
    合并过程中,会比较两个子数组中的元素,并按照大小顺序依次放入新数组中,直到两个子数组中的所有元素都被拷贝完毕。
    这个合并过程是递归进行的,每次合并两个子数组,生成的新有序数组又会被视为新的子数组,继续参与后续的合并过程。
  3. 结束条件:
    当所有子数组都合并完毕,最终得到的数组就是完全有序的。
    这个过程可以想象成不断地拆分和组合:首先把一个大问题(排序整个数组)拆分成许多小问题(排序单个元素的子数组),然后解决这些小问题(它们实际上已经是有序的),最后把这些小问题的解(有序的子数组)合并起来,形成最终的大问题的解(完全有序的数组)。这也就是所谓的分治法。

2、非递归版本

不使用栈或队列的非递归归并排序实现,可以通过一种自底向上的方式来完成。非递归版本避免造成栈溢出。这种方法通常被称为“迭代归并排序”或“自底向上归并排序”。简单说,就是通过控制循环来实现这一过程

  1. 初始化:
    确定待排序数组的长度n。
    设定一个子数组的大小gap,初始值为1,这表示每个子数组最初只包含一个元素,因此它们自然是有序的。
  2. 合并子数组:
    当gap小于n时,重复以下步骤:
    a. 遍历数组,每次以gap为步长,选择两个相邻的子数组进行合并
    b. 对于每一对相邻的子数组,使用标准的归并过程将它们合并成一个有序数组。
    c. 如果只剩下一个子数组,或者两个子数组的大小不同(因为可能到达数组的末尾),如果end1>=n或者begin2>=n,跳出循环;如果只是end2>=n则改变end2的值为n - 1。
  3. 递增子数组大小:
    将gap翻倍,准备在更大的子数组上进行合并操作。
  4. 重复:
    重复步骤2和3,直到整个数组排序完成。
    溢出示意图

1.越界主要是end1,begin2,end2这三个中一个越界
2.如果end1越界,则后面都越界,就不会进行归并了,begin2也是同理(end1>=n || begin2>=n)
3.如果是end2越界,则直接修正end2到最末尾的下标即可

三、代码实现

1、递归版本

void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	_MergeSort(a, tmp, 0, n - 1);
	free(tmp);
}
void _MergeSort(int* a, int* tmp, int begin, int end)
{
	if (begin >= end)
		return;
	int mid = (end + begin) / 2;
	_MergeSort(a, tmp, begin, mid);
	_MergeSort(a, tmp, mid + 1, end);
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] > a[begin2])
			tmp[i++] = a[begin2++];
		else tmp[i++] = a[begin1++];
	}
	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}
	memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}

2、非递归版本

void MergeSortNonR(int* a, int n)
{
	int gap = 1;
	int* tmp = (int*)malloc(sizeof(int) * n);
	while (gap < n)
	{
		int j = 0;
		for (int i = 0; i < n; i += 2 * gap)
		{
			int begin1 = i, end1 = begin1 + gap - 1;
			int begin2 = begin1 + gap, end2 = begin2 + gap - 1;
			if (end1 > n - 1 || begin2 > n - 1)
				break;
			else if (end2 > n - 1)
				end2 = n - 1;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] > a[begin2])
					tmp[j++] = a[begin2++];
				else tmp[j++] = a[begin1++];
			}
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}
			memcpy(a + i, tmp + i, sizeof(int) * (2 * gap));
		}
		gap *= 2;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值