1、引例
在说明归并排序之前,我们先看看这样的一个问题:
给出两个序列Ln1,Rn2(Ln1,Rn2都为非递减序列)。现在问题是,让你将Ln1和Rn2合并为一个非递减序列。
我们稍微一分析,容易写出下列代码:
void merge(int *L, int n1, int *R, int n2, int *arr){
int i = 0, j = 0, k = 0;
while(i < n1 && j < n2){
if(L[i] < R[i]) arr[k++] = L[i++];
else arr[k++] = R[j++];
}
while(i < n1) arr[k++] = L[i++];
while(j < n2) arr[k++] = R[j++];
}
2、归并排序
但是现在我们把问题改成:
给定一无序序列an(n为序列长度),现在让你将该序列变为非递减的序列
显然,现在我们的问题就变为一个排序问题.通过上述的例子,我们不禁会联想到一种排序方法.如果序列an的[0, n/2)与[n/2, n)都排好序了,那么我们直接使用刚才例子的方法合并就可以了.那么接着我们就会思考,一个序列什么时候他的前一半和后一半都是排好序的.稍微一思考我们容易想到当序列长度为2的时候,其前一半和后一半自然就是有序的(因为他的前一半和后一半都各自只有一个元素).那么我们很自然的可以想到,把一个序列不断的分割直到他的长度为1时,我们就开始合并.于是我们就可以得出如下代码:
void merge(int *arr, int start, int mid, int end){
//额外申请空间保存区间[start, mid)与[mid, end)的数
int n1 = mid - start;
int n2 = end - mid;
int *L = new int[n1];
int *R = new int[n2];
int i = 0, j = 0, k = start;
while(i < n1) L[i++] = arr[k++];
while(j < n2) R[j++] = arr[k++];
//将Ln1与Rn2合并
i = j = 0, k = start;
while(i < n1 && j < n2){
if(L[i] < R[j]) arr[k++] = L[i++];
else arr[k++] = R[j++];
}
while(i < n1) arr[k++] = L[i++];
while(j < n2) arr[k++] = R[j++];
//释放Ln1,Rn2的空间
delete[] L;
delete[] R;
L = R = nullptr;
}
void merge_sort(int *arr, int start, int end){
if(start+1 < end){ //区间至少有一个元素
int mid = (start + end) >> 1; //区间中点,将区间一分为二
merge_sort(arr, start, mid); //对区间[start, mid)进行排序
merge_sort(arr, mid, end); //对区间[mid, end)进行排序
merge(arr, start, mid, end); //合并区间[start, mid)和[mid, end)
}
}
这样我们就实现了归并排序。那么我们来分析下其复杂度。首先时间复杂度,我们容易看出它的时间由递归的深度和每一层递归的宽度。由于每次一分为二,递归深度为logn,每一层都有n个元素,其宽度为n。故其时间复杂度为O(nlogn)。我们分析下他的空间复杂度,由于每次归并我们需要额外的空间来保存数据,故可得到归并排序的空间复杂度为O(n)。
3、归并排序求逆序对数
假设a[1...n]是有n个数的序列。若i < j且A[i] > A[j],则对偶(i, j)称为A的逆序对。现在问题是给定一个序列an,问其有多少对逆序对?
看到这个问题我们可以想到,归并排序时,将序列an一分为二Ln1, Rn2。在非降序的排序中,如果L[i] <= R[j],显然没有逆序数,只有当L[i] > R[j]时产生逆序数。此时L[i+1...n1]里的元素均比R[j]大,而R[j]又在它们的后面.所以,此时的逆序对数:n1-i。
我们来一道题练练手:POJ1804 Brainman。附上该题代码:
#include <cstdio>
#define MAXN (1000)
int inversion, a[MAXN];
void merge(int *arr, int start, int mid, int end){
int n1 = mid - start;
int n2 = end - mid;
int *L = new int[n1];
int *R = new int[n2];
int i = 0, j = 0, k = start;
while(i < n1) L[i++] = arr[k++];
while(j < n2) R[j++] = arr[k++];
i = j = 0, k = start;
while(i < n1 && j < n2){
if(L[i] > R[j]) arr[k++] = R[j++], inversion += n1 - i;//加上逆序对
else arr[k++] = L[i++];
}
while(i < n1) arr[k++] = L[i++];
while(j < n2) arr[k++] = R[j++];
delete[] L;
delete[] R;
L = R = NULL;
}
void merge_sort(int *arr, int start, int end){
if(start+1 < end){
int mid = (start + end) >> 1;
merge_sort(arr, start, mid);
merge_sort(arr, mid, end);
merge(arr, start, mid, end);
}
}
int main(){
int T, n;
scanf("%d", &T);
for(int nCase = 1; nCase <= T; nCase++){
scanf("%d", &n);
for(int i = 0; i < n; i++)
scanf("%d", &a[i]);
inversion = 0;
merge_sort(a, 0, n);
printf("Scenario #%d:\n%d\n\n", nCase, inversion);
}
return 0;
}