Feeling

              三人行,必有我師焉

             ::  :: 新隨筆 :: 聯系 ::  :: 管理 ::
            185 隨筆 :: 0 文章 :: 392 評論 :: 0 Trackbacks

          1 歸并排序(MergeSort)

          歸并排序最差運行時間是O(nlogn),它是利用遞歸設計程序的典型例子。

          歸并排序的最基礎的操作就是合并兩個已經排好序的序列。

          假設我們有一個沒有排好序的序列,那么首先我們使用分割的辦法將這個序列分割成一個一個已經排好序的子序列。然后再利用歸并的方法將一個個的子序列合并成排序好的序列。分割和歸并的過程可以看下面的圖例。



          從上圖可以看出,我們首先把一個未排序的序列從中間分割成2部分,再把2部分分成4部分,依次分割下去,直到分割成一個一個的數據,再把這些數據兩兩歸并到一起,使之有序,不停的歸并,最后成為一個排好序的序列。

          如何把兩個已經排序好的子序列歸并成一個排好序的序列呢?可以參看下面的方法。

          假設我們有兩個已經排序好的子序列。
          序列A:1 23 34 65
          序列B:2 13 14 87
          那么可以按照下面的步驟將它們歸并到一個序列中。

          (1)首先設定一個新的數列C[8]。
          (2)A[0]和B[0]比較,A[0] = 1,B[0] = 2,A[0] < B[0],那么C[0] = 1
          (3)A[1]和B[0]比較,A[1] = 23,B[0] = 2,A[1] > B[0],那么C[1] = 2
          (4)A[1]和B[1]比較,A[1] = 23,B[1] = 13,A[1] > B[1],那么C[2] = 13
          (5)A[1]和B[2]比較,A[1] = 23,B[2] = 14,A[1] > B[2],那么C[3] = 14
          (6)A[1]和B[3]比較,A[1] = 23,B[3] = 87,A[1] < B[3],那么C[4] = 23
          (7)A[2]和B[3]比較,A[2] = 34,B[3] = 87,A[2] < B[3],那么C[5] = 34
          (8)A[3]和B[3]比較,A[3] = 65,B[3] = 87,A[3] < B[3],那么C[6] = 65
          (9)最后將B[3]復制到C中,那么C[7] = 87。歸并完成。

          如果我們清楚了上面的分割和歸并過程,那么我們就可以用遞歸的方法得到歸并算法的實現。

              public class MergeSorter
              {
                  
          private static int[] myArray;
                  
          private static int arraySize;

                  
          public static void Sort( int[] a )
                  {
                      myArray 
          = a;
                      arraySize 
          = myArray.Length;
                      MergeSort();
                  }

                  
          /// <summary>
                  
          /// 利用歸并的方法排序數組,首先將序列分割
                  
          /// 然后將數列歸并,這個算法需要雙倍的存儲空間
                  
          /// 時間是O(nlgn)
                  
          /// </summary>
                  private static void MergeSort()
                  {
                      
          int[] temp = new int[arraySize];
                      MSort( temp, 
          0, arraySize - 1);
                  }

                  
          private static void MSort(int[] temp, int left, int right)
                  {
                      
          int mid;

                      
          if (right > left)
                      {
                          mid 
          = (right + left) / 2;
                          MSort( temp, left, mid); 
          //分割左邊的序列
                          MSort(temp, mid+1, right);//分割右邊的序列
                          Merge(temp, left, mid+1, right);//歸并序列
                      }
                  }

                  
          private static void Merge( int[] temp, int left, int mid, int right)
                  {
                      
          int i, left_end, num_elements, tmp_pos;

                      left_end 
          = mid - 1;
                      tmp_pos 
          = left;
                      num_elements 
          = right - left + 1;

                      
          while ((left <= left_end) && (mid <= right)) 
                      {
                          
          if (myArray[left] <= myArray[mid]) //將左端序列歸并到temp數組中
                          {
                              temp[tmp_pos] 
          = myArray[left];
                              tmp_pos 
          = tmp_pos + 1;
                              left 
          = left +1;
                          }
                          
          else//將右端序列歸并到temp數組中
                          {
                              temp[tmp_pos] 
          = myArray[mid];
                              tmp_pos 
          = tmp_pos + 1;
                              mid 
          = mid + 1;
                          }
                      }

                      
          while (left <= left_end) //拷貝左邊剩余的數據到temp數組中
                      {
                          temp[tmp_pos] 
          = myArray[left];
                          left 
          = left + 1;
                          tmp_pos 
          = tmp_pos + 1;
                      }
                      
          while (mid <= right) //拷貝右邊剩余的數據到temp數組中
                      {
                          temp[tmp_pos] 
          = myArray[mid];
                          mid 
          = mid + 1;
                          tmp_pos 
          = tmp_pos + 1;
                      }

                      
          for (i=0; i < num_elements; i++//將所有元素拷貝到原始數組中
                      {
                          myArray[right] 
          = temp[right];
                          right 
          = right - 1;
                      }
                  }
              }


          歸并排序算法是一種O(nlogn)的算法。它的最差,平均,最好時間都是O(nlogn)。但是它需要額外的存儲空間,這在某些內存緊張的機器上會受到限制。

          歸并算法是又分割和歸并兩部分組成的。對于分割部分,如果我們使用二分查找的話,時間是O(logn),在最后歸并的時候,時間是O(n),所以總的時間是O(nlogn)。

          2 堆排序(HeapSort)

          堆排序屬于百萬俱樂部的成員。它特別適合超大數據量(百萬條記錄以上)的排序。因為它并不使用遞歸(因為超大數據量的遞歸可能會導致堆棧溢出),而且它的時間也是O(nlogn)。還有它并不需要大量的額外存儲空間。

          堆排序的思路是:

          (1)將原始未排序的數據建成一個堆。
          (2)建成堆以后,最大值在堆頂,也就是第0個元素,這時候將第零個元素和最后一個元素交換。
          (3)這時候將從0到倒數第二個元素的所有數據當成一個新的序列,建一個新的堆,再次交換第一個和最后一個元素,依次類推,就可以將所有元素排序完畢。

          建立堆的過程如下面的圖所示:


          堆排序的具體算法如下:

          public class HeapSorter 
              {
                  
          private static int[] myArray;
                  
          private static int arraySize;

                  
          public static void Sort( int[] a )
                  {
                      myArray 
          = a;
                      arraySize 
          = myArray.Length;
                      HeapSort();
                  }

                  
          private static void HeapSort()
                  {
                      BuildHeap();            
          //將原始序列建成一個堆

                      
          while ( arraySize > 1 )
                      {
                          arraySize
          --;
                          Exchange ( 
          0, arraySize );//將最大值放在數組的最后
                          DownHeap ( 0 );  //將序列從0到n-1看成一個新的序列,重新建立堆
                      } 
                  }

                  
          private static void BuildHeap()
                  {
                      
          for (int v=arraySize/2-1; v>=0; v--)
                          DownHeap ( v );
                  }

                  
          //利用向下遍歷子節點建立堆
                  private static void DownHeap( int v )
                  {
                      
          int w = 2 * v + 1;                     // 節點w是節點v的第一個子節點

                      
          while (w < arraySize)
                      {
                          
          if ( w+1 < arraySize )        // 如果節點v下面有第二個字節點
                              if ( myArray[w+1> myArray[w] ) 
                                  w
          ++;                        // 將子節點w設置成節點v下面值最大的子節點

                           
          // 節點v已經大于子節點w,有了堆的性質,那么返回
                          if ( myArray[v] >= myArray[w] ) 
                              
          return;   
                          
                          Exchange( v, w );     
          // 如果不是,就交換節點v和節點w的值
                          v = w;        
                          w 
          = 2 * v + 1;            // 繼續向下找子節點
                      }
                  }

                  
          //交換數據
                  private static void Exchange( int i, int j )
                  {
                      
          int t = myArray[i];
                      myArray[i] 
          = myArray[j];
                      myArray[j] 
          = t;
                  }
              }    


           

          堆排序主要用于超大規模的數據的排序。因為它不需要額外的存儲空間,也不需要大量的遞歸。

          3 幾種O(nlogn)算法的初步比較

          我們可以從下表看到幾種O(nlogn)算法的效率的區別。所有的數據都使用.Net的Random類產生,每種算法運行100次,時間的單位為毫秒。


          500隨機整數5000隨機整數20000隨機整數
          合并排序0.31251.56257.03125
           Shell排序0.31251.256.875
          堆排序0.468752.18756.71875
          快速排序0.156250.6252.8125

          從上表可以明顯地看出,快速排序是最快的算法。這也就給了我們一個結論,對于一般的應用來說,我們總是選擇快速排序作為我們的排序算法,當數據量非常大(百萬數量級)我們可以使用堆排序,如果內存空間非常緊張,我們可以使用Shell排序。但是這意味著我們不得不損失速度。 

          /******************************************************************************************
           *【Author】:flyingbread
           *【Date】:2007年2月2日
           *【Notice】:
           *1、本文為原創技術文章,首發博客園個人站點(http://flyingbread.cnblogs.com/),轉載和引用請注明作者及出處。
           *2、本文必須全文轉載和引用,任何組織和個人未授權不能修改任何內容,并且未授權不可用于商業。
           *3、本聲明為文章一部分,轉載和引用必須包括在原文中。
           ******************************************************************************************/
          posted on 2012-11-10 23:18 三人行,必有我師焉 閱讀(628) 評論(2)  編輯  收藏

          評論

          # re: 排序1+4:歸并排序(MergeSort)和堆排序(HeapSort)(轉) 2012-11-11 01:52 三人行,必有我師焉
          堆排序,首先建立一個大頂堆,從最底層的葉子節點開始建(數組尾端),首先最底層的葉子右節點和左節點比較,取出較大的那個葉子節點,讓這個節點和父親比較,如果大于父親,則和父親交換。底層葉子遍歷比較完之后,父節點遍歷比較,直到根節點(數組頭)。

          建立完大頂堆之后,開始遍歷,因為最大的節點就是根節點,直接把根節點和最底層葉子交換,然后重新構建大頂堆,這個大頂堆已經是有序的了(不包括已交換的部分),除了根節點外,其他部分都是大頂堆構造,此時先讓根節點的左孩子和右孩子比較,大的那個孩子和父節點交換,交換后繼續遞歸比較,看看被交換的根節點交換后還是小于子節點,如果還是小,則繼續交換,直到大于子節點為止。那么剩下的堆就又是個大頂堆了,然后循環構建n-1次即可。  回復  更多評論
            

          # re: 排序1+4:歸并排序(MergeSort)和堆排序(HeapSort)(轉) 2012-11-12 01:16 三人行,必有我師焉
          原地快速排序,把數組需要需要排序的部分分成左邊和右邊兩部分,但是如何讓數組分成左邊和右邊兩塊呢?
          1.以數組最右端的元素作為分割點
          2.做一個標記符,標記已經放了幾個元素到左邊了
          3.開始遍歷數組每個元素,碰到小于分割點的元素,就和第(標記符+1)個元素交換,然后標記符增加1。
          4.將分割點和第(標記符+1)個元素交換,這是第(標記符+1)個元素左邊的元素都小于分割點,右邊的元素都大于或等于分割點元素。
          5.遞歸排序分割點左邊的部分和右邊的部分,直到子數組的左邊部分索引和右邊部分索引相等,也就是長度為1為止。  回復  更多評論
            


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          GitHub |  開源中國社區 |  maven倉庫 |  文件格式轉換 
          主站蜘蛛池模板: 彭州市| 奈曼旗| 凌海市| 舞钢市| 开封县| 昌吉市| 绵竹市| 嵊州市| 秦皇岛市| 镇坪县| 黎川县| 九龙坡区| 瑞安市| 荔浦县| 洛浦县| 栾城县| 左权县| 兰坪| 桐乡市| 唐海县| 怀柔区| 玛多县| 琼结县| 从化市| 罗定市| 莱州市| 来安县| 冀州市| 环江| 从江县| 许昌市| 仙游县| 太湖县| 宜兰市| 永泰县| 长武县| 开化县| 临湘市| 喀什市| 灌云县| 孝感市|