第2篇 歸併排序

歸併排序

歸併思想:將兩個有序的數組歸併成一個更大的有序數組。

優點:保證將任意長度N的數組排序所需時間和NlogN成正比。

缺點:所需的額外空間和N成正比。

1 原地歸併的抽象方法

所謂原地歸併,既是將連個已經有序的子序列合併爲一個序列。

/*
 *需要額外的數組大小的空間
 *主要是對四種情況的比較:
 *1.左半邊用盡
 *2.右半邊用盡
 *3.右半邊當前元素小於當前元素
 *4.右半邊當前元素大於當前元素
 */
void Merge(int *a, int low, int mid, int high){
    //將a中的元素都複製到aux中去。
    for (int k = low; k <= high; ++k)
        aux[k] = a[k];
    //再將aux中的元素排序到a中
    int i = low;
    int j = mid + 1;
    for (int k = low; k <= high; ++k){
        if (i > mid)
            a[k] = aux[j++];
        else if (j > high)
            a[k] = aux[i++];
        else if (aux[j] < aux[i])   //這裏一開始寫成(a[j] < a[i]),調試了1個小時,打臉!!!
            a[k] = aux[j++];
        else
            a[k] = aux[i++];
    }
}

2 自頂向下的歸併排序

對於N個數據來講,劃分爲n層,對於0~n-1之間的任意k,自頂向下第k層有2的k次方個數組,每個數組長度爲2的(n-k)次方,歸併最多需要2的(n-k)次比較,因此,每層的比較次數爲2的n次方,n層總共爲n*2的n次 = NlgN。

下面給出測試例子:

#include <iostream>
using namespace std;

typedef void(*Fp)(int *a, int count);

//根據<<Effective C++>> 條款02 : 儘量使用const,enum,inline替換#define
const int Count = 10;
int aux[Count];

//原地歸併的抽象方法
void Merge(int *a, int low, int mid, int high);
//自頂向下的歸併排序
void merge_sort(int *a, int count);

void _merge_sort(int *a, int low, int high);

int main()
{
    int a[] = { 23, 3, 45, 123, 2, 1, 342, 52, 452, 22 };
    //排序前:
    cout << "排序前:" << endl;
    for (int i = 0; i < Count; ++i)
        cout << a[i] << " ";
    cout << endl;

    Fp fp = merge_sort;

    fp(a, Count);

    //排序後:
    cout << "排序後:" << endl;
    for (int i = 0; i < Count; ++i)
        cout << a[i] << " ";
    cout << endl;

    return 0;
}


/*
 *需要額外的數組大小的空間
 *主要是對四種情況的比較:
 *1.左半邊用盡
 *2.右半邊用盡
 *3.右半邊當前元素小於當前元素
 *4.右半邊當前元素大於當前元素
 */
void Merge(int *a, int low, int mid, int high){
    //將a中的元素都複製到aux中去。
    for (int k = low; k <= high; ++k)
        aux[k] = a[k];
    //再將aux中的元素排序到a中
    int i = low;
    int j = mid + 1;
    for (int k = low; k <= high; ++k){
        if (i > mid)
            a[k] = aux[j++];
        else if (j > high)
            a[k] = aux[i++];
        else if (aux[j] < aux[i])   //這裏一開始寫成(a[j] < a[i]),調試了1個小時,打臉!!!
            a[k] = aux[j++];
        else
            a[k] = aux[i++];
    }
}

void merge_sort(int *a, int count){
    _merge_sort(a, 0, count - 1);
}

void _merge_sort(int *a, int low, int high){
    //首先判斷是否只有1個元素
    if (high <= low)
        return;
    int mid = low + (high - low) / 2;
    //歸併的排列左半邊
    _merge_sort(a, low, mid);
    //歸併的排列右半邊
    _merge_sort(a, mid + 1, high);
    //將兩邊合併
    Merge(a, low, mid, high);
}

這種排序是高效算法設計中分治思想最典型的一個例子

下面給出幾種性質:

1)對於長度爲N的任意數組,自頂向下的歸併排序需要1/2NlgN至NlgN次比較。

2)對於長度爲N的任意數組,自頂向下的歸併排序最多需要訪問數組6NlgN次。

對於自頂向下的歸併排序有以下幾點可以優化的地方:

1)對於小規模子數組使用插入排序(比如長度小於15,一般可以將時間縮短10%到15%)。

2)測試數組是否已經有序。(如果a[mid]小於等於a[mid+1],就認爲數組已經是有序的並跳過merge()方法)。

3)不將元素複製到輔助數組(時間、空間不行)。

3 自底向上的歸併排序

先上源碼:

void mergeBU_sort(int *a, int count){
    int end = 0;
    for (int sz = 1; sz < Count; sz = sz + sz){                     //sz表示子數組的大小
        for (int lo = 0; lo < Count - sz; lo += sz + sz){           //lo:子數組的索引
            end = (lo + sz + sz - 1) < (Count - 1) ? (lo + sz + sz - 1) : (Count - 1);
            Merge(a, lo, lo + sz - 1, end);
        }
    }
}

思想:首先進行兩兩歸併,然後四四歸併,然後八八歸併。。。

對於長度爲N的任意數組,自底向上的歸併排序需要1/2NlgN至NlgN次比較,最多訪問數組6NlgN次。

優勢:自底向上的歸併排序比較適合用鏈表組織的數據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章