歸併排序

在介紹歸併排序之前,首先需要介紹一下分治法的思想

很多算法在結構上是遞歸的,算法需要一次或多次調用自身來解決相應的子問題,即將原問題劃分成n個規模較小而結構與原問題相似的子問題,遞歸的解決這些子問題,然後合併其結果,就得到原問題的解。

分解:將原問題分解爲一系列子問題

解決:遞歸解決各個子問題。若子問題足夠小,則直接解決

合併:將子問題結果合併成原問題的解。


歸併排序其實就是應用了分治法的一個典型實例,直觀的操作如下:

分解:將n個元素分成個含n/2個元素的子序列

解決:用歸併排序遞歸的對子序列進行排序

合併:合併兩個子序列得到排序結果

在對子序列進行排序時,其長度爲1時遞歸結束,單個元素被視爲是排好序的。

歸併排序的關鍵步驟在於合併兩個已經排好序的子序列。爲做排序,引入一個輔助過程MERGE(A,p,q,r),A是數組,p,q,r是下標,滿足p<=q<r,假設A[p..q]和A[q+1..r]都已經排好序了,將他們合併成一個子數組A[p..r]。

對於這個函數的工作過程,想象一下桌面上有兩堆已經排好序的撲克牌,那麼怎麼講這兩堆牌合併成一堆呢?只需要每一次都從這兩堆牌中取出較小的那一個放到一個新的撲克牌堆中(剛開始必然什麼都沒有),重複以上過程,知道有一堆牌空了,此時,只需要將剩下的那堆牌直接扔到新的堆中就可以了。



在具體的代碼實現中,略微有些變化,爲了避免檢查每一個堆是否是空的,在每一個堆的底部都放上一張“哨兵牌”,包含了一個特殊值(自己設置,可以使用無窮大,目的就是爲了區分)。當一個堆中出現了哨兵牌後,由於它的值大於所有的牌,每次選取的時候只能選擇另一個堆中的牌,直到同時出現兩張哨兵牌。當然,由於我們預先知道了會有r-p+1張牌會輸出到堆中,所以一旦執行了r-p+1次後,算法就可以停下來了。

一下是具體實現代碼:

#include <stdio.h>
#include <stdlib.h>

//定義INF變量用於哨兵中,假設在一個大小爲MAX的數組中實現歸併排序
#define INF 10000
#define MAX 10

//三個參數A表示待排序的數組,其中,A[p..q]和A[q+1..r]已經排序完畢
void MERGE(int* A,int p,int q,int r)
{
    const int n1=q-p+1;const int n2=r-q;
    int L[n1+1];int R[n2+1];
    int i,j,k;
    for(i=0;i<n1;i++)
        L[i]=A[p+i];
    for(j=0;j<n2;j++)
        R[j]=A[q+j+1];
    L[n1]=INF;R[n2]=INF;
    i=j=0;
    //接下來就是講兩個堆合併成一個堆的過程,記住,k的取值範圍是p到r(調這個錯誤花了老子一下午)
    for(k=p;k<=r;k++)
    {
        if(L[i]<=R[j])
        {
            A[k]=L[i];
            i++;
        }
        else
        {
            A[k]=R[j];
            j++;
        }
    }
}

//歸併排序算法,分而治之
void Merge_Sort(int* A,int p,int r)
{
    int q;
    if(p<r)
    {
        q=(p+r)/2;
        Merge_Sort(A,p,q);
        Merge_Sort(A,q+1,r);
        MERGE(A,p,q,r);
    }
}

//主函數測試數據
int main()
{
    int count;
    int A[MAX]={2,41,6,18,10,12,33,5,7,91};
    Merge_Sort(A,0,9);
    for(count=0;count<MAX;count++)
        printf("%d ",A[count]);
    printf("\n");
    return 0;
}


發佈了35 篇原創文章 · 獲贊 35 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章