歸併排序

二路歸併排序

            1,遞歸思想

假設初始列表含有n個記錄,則可以看成n個有序子序列,每個有序子序列的長度爲1,然後兩兩歸併,得到n/2個長度爲2的有序子序列,再兩兩歸併,如此重複下去,直到得到一個長度爲n的有序序列爲止。

            2,遞歸實現二路歸併排序 
#define MAXSIZE 10  //定義一個宏,是arr的大小,也是需要開闢數組的大小

//二路歸併,將TR2[m..t]和[t+1..n]歸併成有序序列
void Merge(int TR1[], int TR2[],int m, int t, int n)
{
    int j,k;//j爲TR1[]的下標,j爲TR2[t+1..n]的下標,而m爲TR2[m..t]的下標
    //將TR2中記錄由小到大歸併入TR1中
    for(k = m,j = t+1; m <= t && j <= n; ++k)
    {
        if(TR2[m] < TR2[j])
            TR1[K] = TR2[m++];
        else
            TR1[K] = TR2[j++];
    }
    //將剩餘的TR2[m..t]複製到TR1中
    for(; m <= t; ++m,++k)
        { TR1[k] = TR2[m]; }
    //將剩餘的TR2[t+1..n]複製到TR1中
    for(; j <= n; ++j,++k)
        { TR1[k] = TR2[j]; }
}
//該函數將大量數據遞歸下去,直到數據有序
void MSort(int SR[], int TR1[], int m, int n)
{
    int t; //中間值,來劃分歸併的前半部分和後半部分
    int TR2[MAXSIZE];//輔助數組,用來存儲排歸併排序後的有序子序列
    if(m == n)//遞歸終止條件  m==n時,此趟歸併完成
        TR1[m] = SR[m];
    else
    {
        t = (m+n) / 2; //將SR分爲[m..t]和[t+1..n]
        Msort(SR,TR2,m,t);//將SR[m..t]歸併排序成有序數組放到TR2[m..t]
        Msort(SR,TR2,t+1,n);//將SR[t+1..n]歸併排序成有序數組放到TR2[t+1..n]
        Merge(TR1,TR2,m,t,n);//最後將TR2中的[m..t]與[t+1..n]合併成有序數組放到TR1,則TR1保存的便是歸併排序後的結果。
    }
}

//該函數只是封裝了一個需要遞歸的函數
void MergeSort(int *arr, int n)
{
    assert(arr != NULL && n > 1);
    MSort(arr,arr,0,n-1);
}
        3,歸併排序遞歸算法的時間複雜度 

時間複雜度爲O(nlogn)
一趟排序需要將SR[0]~SR[n-1]中相鄰的長度爲h的有序子序列進行兩兩歸併,並將結果放入TR1[0]~TR1[n-1]中,這需要將待排序序列掃描一遍,耗費時間爲O(n),根據二叉樹的性質,整個歸併排序需要logn次,所以歸併排序的時間複雜度爲O(nlogn)。

空間複雜度爲O(n+logn),存儲交換結果的數組已經遞歸的棧空間。
歸併排序特點:空間比較佔內存,但排序效率高且穩定。

歸併排序的遞歸算法,大量的引用的遞歸,儘管代碼比較清晰,但其會消耗時間和空間上的性能,我們追求效率,所以讓我們來看看迭代方法。

         4,歸併排序的迭代實現 
#define MAXSIZE 10
//將SR[m..t]和[t+1..n]歸併的結果放入TR中
void Merge(int SR, int TR,int m, int t, int n)
{
    int j,k;
    for(j = t+1,k = m;m <= t && j <= n; ++k)
    {
        if(SR[m] < SR[j])
            TR[k] = SR[m++];
        else
            TR[k] = SR[j++];
    }

    for(;m <= t; ++m,++k)
    {
        TR[k] = SR[m];
    }
    for(;j <= n; ++j,++k)
    {
        TR[k] = SR[j];
    }

}

//將相鄰長度爲s的子序列歸併到TR中
void MergePass(int SR[], int TR[],int s, int n)
{
    int i = 0;
    int j;
    while(i <= n - 2*s +1)
    {
        Merge(SR,TR,i,i+s-1,i+2*s-1);//兩兩歸併
        i = i + 2 * s;  
    }
    if(i < n-s+1) //歸併最後兩個序列
    {
        Merge(SR,TR,i,i+s-1,n);
    }
    else  //歸併最後剩下的單個子序列
    {
        for(j = i; j < n; ++j)
            TR[j] = SR[j];
    }
}

void NiceMergeSort(int *arr, int n)
{
    assert(arr != NULL && n > 1);
    int *TR = (int*) malloc(sizeof(int) * MAXSIZE);
    int k = 1; //k爲歸併長度  第一次將相隔爲1的數據進行歸併
    while(k < MAXSIZE-1)
    {
        //將原來無序的數列兩兩歸併入TR
        MergePass(arr,TR,k,MAXSIZE-1);
        k *= 2;  //歸併長度擴大一倍
        //將兩兩歸併有序的數列,再次歸併入數組arr中
        MergePass(TR,arr,k,MAXSIZE-1);
        k *= 2;
    }
}
        5,非遞歸迭代的複雜度

非遞歸迭代的方法,避免了遞歸時深度爲logn的棧空間,空間上只申請了臨時數組TR,空間複雜度爲O(n),避免遞歸也在時間複雜度上有了一定的提升,因此,我們使用歸併排序,應該多使用迭代方法。

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