算法:归并排序

归并排序是一种非常经典的分治算法,也是非常精美的算法。学习归并排序,对于理解分治法思想、提高算法思维能力十分有帮助。

例题

洛谷1177 排序

题目描述
将读入的 N 个数从小到大排序后输出。

输入格式
第 1 行为一个正整数 N。
第 2 行包含 N 个空格隔开的正整数 a[i],为你需要进行排序的数,数据保证了a[i]不超过10^9。

输出格式
将给定的 N个数从小到大输出,数之间用空格隔开。

输入输出样例
输入

5
4 2 4 5 1

输出

1 2 4 4 5

说明提示
对于20% 的数据,有 N <= 10^3。
对于100% 的数据,有 N <=10^5 。

归并排序

分治算法最重要的三个步骤:

  1. 分解:如果我们要去把整个序列排序,可以去把序列拆成前后两部分(这里取序列中间的数a[mid]为分界点)。
  2. 解决:分别用分治思想再去将拆成的这两个部分进行排序,这里用递归来做。(具体些就是再去把这两个部分拆成4个部分,之后再去不停地拆)。
  3. 合并:每次把前后两已排序好的部分进行合并,合并方法就是新建一个数组ans,之后因为前后两部分都是排好序的,也就都具有单调性,每次把前后两个序列的首元素较小的先放到ans中,之后把这个首元素在原序列中删去,直到两序列都为空。

这里再把合并的思路说具体一些:设t1 = l,t2 = mid + 1分别表示,前部分当前剩余的元素有a[t1] ~ a[mid],后部分当前剩余的元素有a[t2] ~ a[r]。每次如果a[t1] < a[t2],就在ans数组后面加一个a[t1],之后t1++;否则就在ans数组后面加一个a[t2],之后t2++。

还有递归的边界条件,就是当序列为空或只有一个元素时返回,即l >= r时return。

举个例子,便于理解。
初始状态:6,202,100,301,38,8,1
第一次归并后:{6,202},{100,301},{8,38},{1}
第二次归并后:{6,100,202,301},{1,8,38}
第三次归并后:{1,6,8,38,100,202,301}

最后算一下这个算法的时间复杂度:每次拆分是log2(n)次,合并的话n次就行,所以平均时间复杂度是O(n * log2(n))级别。

代码

# include <cstdio>
# include <cmath>
# include <cstring>
# include <algorithm>

using namespace std;

const int N_MAX = 100000;

int n;
int a[N_MAX + 10];

void mergeArray(int l, int mid, int r)
{
	int ans[N_MAX + 10], len = 0;
	int t1 = l, t2 = mid + 1;
	while(t1 <= mid && t2 <= r){
		if(a[t1] < a[t2]) ans[++len] = a[t1++];
		else ans[++len] = a[t2++];
	}
	while(t1 <= mid) ans[++len] = a[t1++];
	while(t2 <= r) ans[++len] = a[t2++];
	for (int i = l; i <= r; i++)
		a[i] = ans[i - l + 1];
}

void mergeSort(int l, int r)
{
	if(l >= r) return;
	int mid = (l + r) / 2;
	mergeSort(l, mid);
	mergeSort(mid + 1, r);
	mergeArray(l, mid, r);
}

int main()
{
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
		scanf("%d", &a[i]);
	mergeSort(1, n);
	for (int i = 1; i <= n; i++)
		printf("%d ", a[i]);
	printf("
");
	return 0;
}
原文地址:https://www.cnblogs.com/000zwx000/p/12330618.html