排序(2)--選擇排序,歸併排序和基數排序

fan_rockrock發表於2014-02-23

一.選擇排序:

        1.簡單選擇排序:

            (1).思想: 首先通過 n –1 次關鍵字比較,從 n 個記錄中找出關鍵字最小的記錄,將它與第一個記錄交換。 
                             再通過 n –2 次比較,從剩餘的 n –1 個記錄中找出關鍵字次小的記錄,將它與第二個記錄交換。 
                             重複上述操作,共進行 n –1 趟排序後,排序結束。

            (2)實現:                         

void SelectSort (SqList &L) {  // 對順序表 L 作簡單選擇排序
           for (i = 1; i < L.length;  ++ i) { 
                 k = i; 
                for ( j = i+1; j <= n; j++)  if (L.r[j].key < L.r[k].key)  k = j; 
                if (i != k)  L.r[i]←→L.r[k];   // 與第 i 個記錄交換
           } 
} // SelectSort 
            (3).演算法分析:

                       時間複雜度:O(n2);

                       空間複雜度:O(1)——交換時用到一個暫存單元

                       穩定性:由於存在著不相鄰元素之間的互換,因此,簡單選擇排序是“不穩定的” 。

            2.樹形選擇排序(競標賽排序):

                       (1)思想:  首先對 n 個記錄的關鍵字進行兩兩比較,得到 n/2個優勝者(關鍵字小者),作為第一步比較的結果保留下來。然後在這 n/2 個較小者之間再進行兩兩比                                         較,…,如此重複,直到選出最小關鍵字的記錄為止

                            

                            

                          .......................


                    (2)演算法分析:

                             時間複雜度:O(nlog2n)-----------n個記錄各自比較約log2n次

                            空間複雜度:O(n) —勝者樹的附加內結點共有n-1個!

                            穩定性:穩定------左右結點相同者左為先

           3.堆排序:

                  (1)概念:設有n個元素的序列k1,k2,…,kn,當且僅當滿足下述關係之一時,稱之為堆

                                    

                                  *樹中所有結點的值均大於(或小於)其左右孩子,此樹的根結點(即堆頂)必最大(或最小) 

                   (2)建堆(建小堆為例):

                                 假若完全二叉樹的某一個結點i,它的左、右子樹已是堆。需要將R[2i].key與R[2i+1].key之中的最小者與R[i].key比較,若R[i].key較大則交換,這有可能破壞下                                一級堆。於是繼續採用上述方法構造下一級堆,直到完全二叉樹中結點i構成堆為止。                    

 void HeapAdjust(SqList &H, int s, int m) 
{//已知H.r[s..m]中記錄的關鍵字除H.r[s].key之外均滿足堆的定義,

 //本函式調整H.r[s]的關鍵字,使H.r[s..m]成為一個大頂堆
  rc = H.r[s]; 
      
    for (j=2*s; j<=m; j*=2)
    {
           
     //沿key較大的孩子結點向下篩選,j為key較大的記錄的下標 
   if(j<m && LT(H.r[j].key, H.r[j+1].key)) ++j;
   if(!LT(rc.key,H.r[j].key)) break; 
          
      H.r[s]=H.r[j]; 
      s=j;   //rc應插入在位置s上   
    }
   H.r[s] = rc;  //插入
 } //HeapAdjust
                    (3)怎樣進行堆排序

                              關鍵:將堆的當前頂點輸出後,如何將剩餘序列重新調整為堆?
                              方法:將當前頂點與堆尾記錄交換,然後仿建堆動作重新調整,如此反覆直至排序結束。

                               對建好的大堆進行排序:

                                         

                                       

                                     .....知道最後記錄是從小到大的。 

 void HeapSort (HeapType &H)
 {  //對順序表H進行堆排序。
   for(i=H.length/2;i>0;--i) //把H.r[1..length]建成大頂堆
    
  HeapAdjust(H,i,H.length);
  for(i=H.length; i>1; --i)
   {  //將堆頂記錄和當前未經排
                 
   //序子序列H.r.[1..i]中最後一個記錄相互交換   
  H.r[1]<---> H.r[i]; 
  HeapAdjust(H,1,i-1); //將H.R[1..i-1]重新調整為大頂堆
   
  }
} //HeapSort 
                    (4)演算法分析;

                              時間效率: O(nlog2n)。因為整個排序過程中需要呼叫n-1次HeapAdjust( )演算法, HeapAdjust( )而演算法本身耗時為log2n;
                              空間效率:O(1)。僅在第二個for迴圈中交換記錄時用到一個臨時變數temp。
                              穩定性: 不穩定。
                              優點:對少量資料效果不明顯,但對大量資料有效

                      


二.歸併排序:

            1.思想:將兩個(或以上)的有序表組成新的有序表。

                        可以把一個長度為n的無序序列看成是 n 個長度為 1 的有序子序列 ,首先做兩兩歸併,得到  n / 2 個長度為 2 的子序列 ;再做兩兩歸併,…,如此重複,直到最後                           得到一個長度為n 的有序序列。

                  

           2.實現:

                一趟歸併排序演算法 (兩路有序併為一路)               

void Merge (SR,&TR,i, m, n) {
       // 將有序的SR[i…m]和SR[m+1…n]歸併為有序的TR[i…n]
    for(k=i , j=m+1;  i<=m && j<=n;  ++k ) {
        if ( SR[i]<= SR[j] )TR[k]=SR[i++];
        else TR[k]=SR[j++];             // 將SR中記錄由小到大地併入TR
    }
   if (i<=m) TR[k…n]=SR[i…m]; // 將剩餘的SR[i…m]複製到TR
   if (j<=n) TR[k…n]=SR[j…n]; // 將剩餘的SR[j…n]複製到TR
}  // Merge
                遞迴形式的兩路歸併排序演算法

 void MSort (SR,&TR1,s, t) {
       // 將無序的SR[s…t]歸併排序為TR1[s…t]
     if ( s==t )TR1[s]=SR[s];        // 當len=1時返回
     else {  
         m=(s+t)/2;   // 將SR [s…t]平分為SR [s…m]和SR [m+1…t]
        MSort (SR,&TR2,s, m);    // 將SR 一分為二, 2分為4…
                               // 遞迴地將SR [s…m]歸併為有序的TR2[s…m]
        MSort (SR,&TR2,m+1, t );
                              // 遞迴地將SR [m+1…t]歸併為有序的TR2[m+1…t]
       Merge(TR2, TR1, s, m, t );
                       // 將TR2 [s…m]和TR2 [m+1…t]歸併到TR1 [s…t]
 } 
}  // MSort
*初次呼叫時為(L, TR, 1, length)

            3.演算法分析:

                    時間效率  :O(nlog2n) 一趟歸併排序的操作是:呼叫[n/2h]次演算法merge將SR[1..n]中前後相鄰且長度為h的有序段進行兩兩歸併,得到前後相鄰長度為2h的有序段,                                         並存放在TR[1..n]中,整個歸併排序需要進行[log2n]趟,所以演算法總的時間複雜度為O(nlog2n)。
                   空間效率 :O(n)  因為需要一個與原始序列同樣大小的輔助序列(TR)。這正是此演算法的缺點。
                   穩定性:穩定


三.基數排序:

        1.兩種思路:

                 最高位優先法MSD (Most Significant Digit first)

                 最低位優先法LSD (Least Significant Digit first)

                  例:對一副撲克牌該如何排序?
                            答:若規定花色為第一關鍵字(高位),面值為第二關鍵字(低位),則使用MSD和LSD方法都可以達到排序目的。
                                        MSD方法的思路:先設立4個花色“箱”,將全部牌按花色分別歸入4個箱內(每個箱中有13張牌);然後對每個箱中的牌按面值進行插入排序(或其它穩                                                                            定演算法)。

                                         LSD方法的思路:先按面值分成13堆(每堆4張牌),然後對每堆中的牌按花色進行排序(用插入排序等穩定的演算法)。

        2.計算機實現:

                 再看一例:

                        例:初始關鍵字序列T=(32, 13, 27, 32*, 19,33),請分別用MSD和LSD進行排序,並討論其優缺點。

                           法1(MSD):原始序列:32,  13,      27,     32*,  19,  33
                                                      先按高位Ki1 排序:(13,  19),  27,  (32,   32*,33)
                                                       再按低位 Ki2排序 :   13,   19 , 27,      32,   32*,   33

                                                      因為有分組,故此演算法需遞迴實現

                          法2(LSD): 原始序列:  32,   13,   27,  32*, 19 ,33
                                                      先按低位Ki2排序: 32,   32*, 13,  33,   27,    19 
                                                      再按高位Ki1排序: 13,   19 ,  27,  32,   32*,  33

                                                      無需分組,易程式設計實現!

          3.LSD實現(順序表);

                        

                       

                         排序時經過了反覆的“分配”和“收集”過程。當對關鍵字所有的位進行掃描排序後,整個序列便從無序變為有序了。

              討論:所用佇列是順序結構,浪費空間,能否改用鏈式結構?

             4.LSD實現(鏈式)

                   

                  

                  

                 

            5.演算法分析;

                      假設有n 個記錄, 每個記錄的關鍵字有d 位,每個關鍵字的取值有radix個, 則需要radix個佇列, 進行d趟“分配”與“收集”。因此時間複雜度:O( d ( n+radix ) )。
                     空間複雜度:O(radix). 基數排序需要增加n+2radix個附加連結指標,空間效率低
                     穩定性:穩定。(一直前後有序)。

相關文章