演算法、排序(9)

來源:互聯網
上載者:User

     歸併排序(Merge Sort)是利用"歸併"技術來進行排序。歸併是指將若干個已排序的子檔案合并成一個有序的檔案。



兩路歸併演算法


1、演算法基本思路


     設兩個有序的子檔案(相當於輸入堆)放在同一向量中相鄰的位置上:R[low..m],R[m+1..high],先將它們合并到一個局部的暫存向量R1(相當於輸出堆)中,待合并完成後將R1複製回R[low..high]中。


(1)合并過程

     合并過程中,設定i,j和p三個指標,其初值分別指向這三個記錄區的起始位置。合并時依次比較R[i]和R[j]的關鍵字,取關鍵字較小的記錄複製到R1[p]中,然後將被複製記錄的指標i或j加1,以及指向複製位置的指標p加1。

     重複這一過程直至兩個輸入的子檔案有一個已全部複製完畢(不妨稱其為空白),此時將另一非空的子檔案中剩餘記錄依次複製到R1中即可。


(2)動態申請R1

     實現時,R1是動態申請的,因為申請的空間可能很大,故須加入申請空間是否成功的處理。


2、歸併演算法


  void Merge(SeqList R,int low,int m,int high)

    {//將兩個有序的子檔案R[low..m)和R[m+1..high]歸併成一個有序的

     //子檔案R[low..high]

     int i=low,j=m+1,p=0; //置初始值

     RecType *R1; //R1是局部向量,若p定義為此類型指標速度更快

     R1=(ReeType *)malloc((high-low+1)*sizeof(RecType));

     if(! R1) //申請空間失敗

       Error("Insufficient memory available!");

     while(i<=m&&j<=high) //兩子檔案非空時取其小者輸出到R1[p]上

       R1[p++]=(R[i].key<=R[j].key)?R[i++]:R[j++];

     while(i<=m) //若第1個子檔案非空,則複製剩餘記錄到R1中

       R1[p++]=R[i++];

     while(j<=high) //若第2個子檔案非空,則複製剩餘記錄到R1中

       R1[p++]=R[j++];

     for(p=0,i=low;i<=high;p++,i++)

       R[i]=R1[p];//歸併完成後將結果複製回R[low..high]

    } //Merge

 


歸併排序

     歸併排序有兩種實現方法:自底向上和自頂向下。


1、 自底向上的方法


(1) 自底向上的基本思想

     自底向上的基本思想是:第1趟歸併排序時,將待排序的檔案R[1..n]看作是n個長度為1的有序子檔案,將這些子檔案兩兩歸併,若n為偶數,則得到
個長度為2的有序子檔案;若n為奇數,則最後一個子檔案輪空(不

參與歸併)。故本趟歸併完成後,前
個有序子檔案長度為2,但最

後一個子檔案長度仍為1;第2趟歸併則是將第1趟歸併所得到的
個有

序的子檔案兩兩歸併,如此反覆,直到最後得到一個長度為n的有序檔案為止。

     上述的每次歸併操作,均是將兩個有序的子檔案合并成一個有序的子檔案,故稱其為"二路歸併排序"。類似地有k(k>2)路歸併排序。


(2) 二路歸併排序的全過程
  【參見動畫示範】


(3) 一趟歸併演算法

 分析:

      在某趟歸併中,設各子檔案長度為length(最後一個子檔案的長度可能小於length),則歸併前R[1..n]中共有
個有序的子檔案:R

[1..length],R[length+1..2length],…,


注意:

     調用歸併操作將相鄰的一對子檔案進行歸併時,必須對子檔案的個數可能是奇數、以及最後一個子檔案的長度小於length這兩種特殊情況進行特殊處理:

  ① 若子檔案個數為奇數,則最後一個子檔案無須和其它子檔案歸併(即本趟輪空);

  ②
若子檔案個數為偶數,則要注意最後一對子檔案中後一子檔案的區間上界是n。

  具體演算法如下:

    void MergePass(SeqList R,int length)

     { //對R[1..n]做一趟歸併排序

      int i;

      for(i=1;i+2*length-1<=n;i=i+2*length)

      Merge(R,i,i+length-1,i+2*length-1);

           //歸併長度為length的兩個相鄰子檔案

      if(i+length-1<n) //尚有兩個子檔案,其中後一個長度小於length

         Merge(R,i,i+length-1,n); //歸併最後兩個子檔案

      //注意:若i≤n且i+length-1≥n時,則剩餘一個子檔案輪空,無須歸併

     } //MergePass


(4)二路歸併排序演算法

  void MergeSort(SeqList R)

   {//採用自底向上的方法,對R[1..n]進行二路歸併排序

     int length;

     for(1ength=1;length<n;length*=2) //做
趟歸併

        MergePass(R,length); //有序段長度≥n時終止

   }

注意:

     自底向上的歸併排序演算法雖然效率較高,但可讀性較差。

2、自頂向下的方法
    
採用分治法進行自頂向下的演算法設計,形式更為簡潔。


(1)分治法的三個步驟

     設歸併排序的當前區間是R[low..high],分治法的三個步驟是:

①分解:將當前區間一分為二,即求分裂點

                



②求解:遞迴地對兩個子區間R[low..mid]和R[mid+1..high]進行歸併排序;

③組合:將已排序的兩個子區間R[low..mid]和R[mid+1..high]歸併為一個有序的區間R[low..high]。

  遞迴的終結條件:子區間長度為1(一個記錄自然有序)。


(2)具體演算法

    void MergeSortDC(SeqList R,int low,int high)

     {//用分治法對R[low..high]進行二路歸併排序

       int mid;

       if(low<high){//區間長度大於1

          mid=(low+high)/2; //分解

          MergeSortDC(R,low,mid); //遞迴地對R[low..mid]排序

          MergeSortDC(R,mid+1,high); //遞迴地對R[mid+1..high]排序

          Merge(R,low,mid,high); //組合,將兩個有序區歸併為一個有序區

        }

     }//MergeSortDC


(3)演算法MergeSortDC的執行過程

     演算法MergeSortDC的執行過程如所示的遞迴樹。

    





二、演算法分析


1、穩定性


      歸併排序是一種穩定的排序。



2、儲存結構要求


     可用順序儲存結構。也易於在鏈表上實現。



3、時間複雜度


     對長度為n的檔案,需進行
趟二路歸併,每趟歸併的時間為O(n),故其時間複雜度無論是在最好情況下還是在最壞情況下均是O(nlgn)。



4、空間複雜度


    
需要一個輔助向量來暫存兩有序子檔案歸併的結果,故其輔助空間複雜度為O(n),顯然它不是就地排序。

 
注意:

     若用單鏈表做儲存結構,很容易給出就地的歸併排序。具體【參見練習】。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.