归并排序是一种非常经典的分治算法,也是非常精美的算法。学习归并排序,对于理解分治法思想、提高算法思维能力十分有帮助。
例题
洛谷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 。
归并排序
分治算法最重要的三个步骤:
- 分解:如果我们要去把整个序列排序,可以去把序列拆成前后两部分(这里取序列中间的数a[mid]为分界点)。
- 解决:分别用分治思想再去将拆成的这两个部分进行排序,这里用递归来做。(具体些就是再去把这两个部分拆成4个部分,之后再去不停地拆)。
- 合并:每次把前后两已排序好的部分进行合并,合并方法就是新建一个数组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;
}