排序——归并排序
1. 思想
归并排序的思想是:如果要对一个数组进行排序,可以先(递归地)将它分成两半分别排序,然后将结果归并起来。归并算法最吸引人的性质是它能保证将任意长度为N的数组排序所需时间和 N l o g N NlogN NlogN 成正比;缺点则是它所需的额外空间和N成正比。
2. 原地归并的抽象方法
将两个不同的有序数组归并起来,可以采用原地归并的方法,这样就可以先将前半部分排序,再将后半部分排序,然后在数组中移动元素而不需要使用额外的空间。
下面就是对原地归并的抽象方法:
// 该方法可以将子数组 a[l...mid] 和 a[mid+1...r]归并成一个有序数组,并存放在a[l...r]
// 子数组 a[l...mid] 和 a[mid+1...r]都是有序的
void merge(int *a, int l, int mid, int r) {
int *aux = new int(r+1); // aux是辅助空间,大小至少为r+1
int i, j, k;
for(i=l; i<=r; i++) { // 将a[l...r]复制到aux[l...r]
aux[i] = a[i];
}
i = l;
j = mid + 1;
for(k=l; k<=r; k++) { // 归并回到a[l...r]
if(i > mid){ // 左半边用尽(取右半边的元素)
a[k] = aux[j++];
}
else if(j > r){ // 右半边用尽(取左半边的元素)
a[k] = aux[i++];
}
else if(aux[i] > aux[j]) { //右半边当前元素小于左半边当前元素(取右半边元素)
a[k] = aux[j++];
}
else { //右半边当前元素大于左半边当前元素(取左半边元素)
a[k] = aux[i++];
}
}
delete[] aux;
}
3. 自顶向下的归并排序(递归)
【思想】
自顶向下的归并排序应用了分治的思想。
【算法过程】
如果要对子数组 a [ l . . . r ] a[l...r] a[l...r] 进行排序,自顶向下的归并排序会先将 a [ l . . . r ] a[l...r] a[l...r] 分为 a [ l . . . m i d ] a[l...mid] a[l...mid] 和 a [ l . . . r ] a[l...r] a[l...r] 两部分,分别通过递归调用将它们单独排序,最后将有序的子数组归并为最终的排序结果。
【代码】
// 对数组a[l..r]进行自顶向下的归并排序
void top_down_sort(int *a, int l, int r) {
if(l >= r) return;
int mid = l + (r - l) / 2;
top_down_sort(a, l, mid); // 将左半边排序
top_down_sort(a, mid+1, r); // 将右半边排序
merge(a, l, mid, r); // 归并结果
}
【算法分析】
对于长度为N的任意数组,自定向下的归并排序:
(1)时间复杂度为 N l o g N NlogN NlogN;
(2)空间复杂度为N;
(3)是稳定排序;
(4)不是原地排序。
4. 自底向上的归并排序(非递归)
【思想】
自底向上的归并排序是一种非递归的方法,它的思想是:先归并那些微型数组,然后再成对归并得到的子数组,如此这般,知道将整个数组归并到一起。
【算法过程】
自底向上的归并排序,首先进行的是两两归并(把每个元素想象成一个大小为1的数组),然后四四归并(将两个大小为2的数组归并成一个4个元素的数组),然后八八归并,一直下去。例如:
对数组[5, 4, 9, 2, 6]进行自底向上的归并排序:
第一轮(两两归并):
(5, 4) -> (4, 5) (9, 2) -> (2, 9)
第二轮(四四归并)
(4, 5)(2, 9) -> (2, 4, 5, 9)
第三轮(八八归并)
(2, 4, 5, 9)(6) -> (2, 4, 5, 6, 9)
在每一轮归并中,最后一次归并的第二个子数组可能比第一个数组要小(例如上例中的第三轮,但这对merge()方法不是问题),否则两个数组应该是大小一样的,而这在下一轮中子数组的大小会翻倍。
【代码】
// 对长度为n的数组a进行自底向上的归并排序
void down_top_sort(int *a, int n) {
int sz, lo;
// 共进行lgN次两两归并
for(sz=1; sz<n; sz+=sz) { // sz是子数组的长度
for(lo=0; lo+sz<n; lo+=sz+sz) { // lo是子数组的索引
merge(a, lo, lo+sz-1, min(n-1, lo+sz+sz-1));
}
}
}
以下例子模拟上述代码过程:
对数组[5, 4, 9, 2, 6]进行自底向上的归并排序:
第一轮(两两归并,sz = 1):
merge(a, 0, 0, 1) merge(a, 2, 2, 3)
第二轮(四四归并,sz = 2)
merge(a, 0, 1, 3)
第三轮(八八归并,sz = 4)
merge(a, 0, 3, 4)
【算法分析】
对于长度为N的任意数组,自定向下的归并排序:
(1)时间复杂度为 N l o g N NlogN NlogN;
(2)空间复杂度为N;
(3)是稳定排序;
(4)不是原地排序。
5. 综合起来
将归并排序综合起来写成一个类,其代码及测试代码如下:
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
#include <vector>
using namespace std;
class MergeSort {
private:
int *aux;
public:
// 自上向底
void top_down_sort(int *a, int l, int r) {
if(l >= r) return;
int mid = l + (r - l) / 2;
top_down_sort(a, l, mid);
top_down_sort(a, mid+1, r);
merge(a, l, mid, r);
}
// 自底向上
void down_top_sort(int *a, int n) {
int sz, lo;
for(sz=1; sz<n; sz+=sz) {
for(lo=0; lo+sz<n; lo+=sz+sz) {
merge(a, lo, lo+sz-1, min(n-1, lo+sz+sz-1));
}
}
}
void merge(int *a, int l, int mid, int r) {
aux = new int(r+1);
int i, j, k;
for(i=l; i<=r; i++) {
aux[i] = a[i];
}
i = l;
j = mid + 1;
for(k=l; k<=r; k++) {
if(i > mid){
a[k] = aux[j++];
}
else if(j > r){
a[k] = aux[i++];
}
else if(aux[i] > aux[j]) {
a[k] = aux[j++];
}
else {
a[k] = aux[i++];
}
}
delete[] aux;
}
};
int main()
{
int a[18] = {16, 7, 13, 2, 8, 4, 3, 54};
int i, j, k;
MergeSort ms;
ms.down_top_sort(a, 8);
for(i=0; i<8; i++) {
cout << a[i] << " " ;
}
cout << endl;
return 0;
}
6. 算法分析
归并排序对于长度为N的任意数组:
(1)时间复杂度为 N l o g N NlogN NlogN;
(2)空间复杂度为N;
(3)是稳定排序;
(4)不是原地排序。