快速排序算法所有版本的c/c++實現

作者:July、二零一一年三月二十日。
出處:http://blog.csdn.net/v_JULY_v
--------------------------------------------------

 

前言:

    相信,經過本人之前寫的前倆篇關於快速排序算法的文章:第一篇、一、快速排序算法,及第二篇、一之續、快速排序算法的深入分析,各位,已經對快速排序算法有了足夠的瞭解與認識。但僅僅停留在對一個算法的認識層次上,顯然是不夠的,即便你認識的有多透徹與深入。最好是,編程實現它。
    而網上,快速排序的各種寫法層次不清,缺乏統一、整體的闡述與實現,即,沒有個一錘定音,如此,我便打算自己去實現它了。

    於是,今花了一個上午,把快速排序算法的各種版本全部都寫程序一一實現了一下。包括網上有的,沒的,算法導論上的,國內教材上通用的,隨機化的,三數取中分割法的,遞歸的,非遞歸的,所有版本都用c/c++全部寫了個遍。
    鑑於時間倉促下,一個人考慮問題總有不周之處,以及水平有限等等,不正之處,還望各位不吝賜教。不過,以下,所有全部c/c++源碼,都經本人一一調試,若有任何問題,懇請指正。

    ok,本文主要分爲以下幾部分內容:

第一部分、遞歸版
一、算法導論上的單向掃描版本
二、國內教材雙向掃描版
      2.1、Hoare版本
      2.2、Hoare的幾個變形版本
三、隨機化版本
四、三數取中分割法
第二部分、非遞歸版

    好的,請一一細看。


第一部分、快速排序的遞歸版本
一、算法導論上的版本
在我寫的第二篇文章中,我們已經知道:
“再到後來,N.Lomuto又提出了一種新的版本,此版本....,即優化了PARTITION程序,它現在寫在了 算法導論 一書上”:

快速排序算法的關鍵是PARTITION過程,它對A[p..r]進行就地重排:

PARTITION(A, p, r)
1  x ← A[r]         //以最後一個元素,A[r]爲主元
2  i ← p - 1
3  for j ← p to r - 1    //注,j從p指向的是r-1,不是r。
4       do if A[j] ≤ x
5             then i ← i + 1
6                  exchange A[i] <-> A[j]
7  exchange A[i + 1] <-> A[r]    //最後,交換主元
8  return i + 1

然後,對整個數組進行遞歸排序:

QUICKSORT(A, p, r)
1 if p < r
2    then q ← PARTITION(A, p, r)   //關鍵
3         QUICKSORT(A, p, q - 1)
4         QUICKSORT(A, q + 1, r)

    根據上述僞代碼,我們不難寫出以下的c/c++程序:
首先是,PARTITION過程:

int partition(int data[],int lo,int hi) 
{
 int key=data[hi];  //以最後一個元素,data[hi]爲主元
 int i=lo-1;
 for(int j=lo;j<hi;j++)   ///注,j從p指向的是r-1,不是r。
 {
  if(data[j]<=key)
  {
   i=i+1;
   swap(&data[i],&data[j]);
  }
 }
 swap(&data[i+1],&data[hi]);   //不能改爲swap(&data[i+1],&key)
 return i+1;
}

補充說明:舉個例子,如下爲第一趟排序(更多詳盡的分析請參考第二篇文章):
第一趟(4步):
   a:3   8   7   1   2   5   6   4   //以最後一個元素,data[hi]爲主元

   b:3   1   7   8   2   5   6   4

   c:3   1   2   8   7   5   6   4

   d:3   1   2   4   7   5   6   8    //最後,swap(&data[i+1],&data[hi])

  而其中swap函數的編寫,是足夠簡單的: 

void swap(int *a,int *b)
{
 int temp=*a;
 *a=*b;
 *b=temp;
}

    然後是,調用partition,對整個數組進行遞歸排序:

void QuickSort(int data[], int lo, int hi)
{
    if (lo<hi)
    {
        int k = partition(data, lo, hi);
        QuickSort(data, lo, k-1);
        QuickSort(data, k+1, hi);
    }

     現在我有一個問題要問各位了,保持其它的不變,稍微修改一下上述的partition過程,如下:

int partition(int data[],int lo,int hi)   //請讀者思考
{
 int key=data[hi];  //以最後一個元素,data[hi]爲主元
 int i=lo-1;
 for(int j=lo;j<=hi;j++)   //現在,我讓j從lo指向了hi,不是hi-1。
 {
  if(data[j]<=key)
  {
   i=i+1;
   swap(&data[i],&data[j]);
  }
 }
 //swap(&data[i+1],&data[hi]);   //去掉這行
 return i;                       //返回i,非i+1.
}

    如上,其它的不變,請問,讓j掃描到了最後一個元素,後與data[i+1]交換,去掉最後的swap(&data[i+1],&data[hi]),然後,再返回i。請問,如此,是否可行?
    其實這個問題,就是我第二篇文章中,所提到的:
    “上述的PARTITION(A, p, r)版本,可不可以改成這樣咧?以下這樣列”:

PARTITION(A, p, r)   //請讀者思考版本。
1  x ← A[r]
2  i ← p - 1
3  for j ← p to r      //讓j 從p指向了最後一個元素r
4       do if A[j] ≤ x
5             then i ← i + 1
6                  exchange A[i] <-> A[j]
//7  exchange A[i + 1] <-> A[r]   去掉此最後的步驟
8  return i      //返回 i,不再返回 i+1.

    望讀者思考,後把結果在評論裏告知我。

    我這裏簡單論述下:上述請讀者思考版本,只是代碼做了以下三處修改而已:1、j從 p->r;2、去掉最後的交換步驟;3、返回 i。首先,無論是我的版本,還是算法導論上的原裝版本,都是準確無誤的,且我都已經編寫程序測試通過了。但,其實這倆種寫法,思路是完全一致的。

    爲什麼這麼說列?請具體看以下的請讀者思考版本,

int partition(int data[],int lo,int hi)   //請讀者思考
{
 int key=data[hi];  //以最後一個元素,data[hi]爲主元
 int i=lo-1;
 for(int j=lo;j<=hi;j++)   //....
 {
  if(data[j]<=key)           //如果讓j從lo指向hi,那麼當j指到hi時,是一定會有A[j]<=x的
  {
   i=i+1;
   swap(&data[i],&data[j]);
  }
 }
 //swap(&data[i+1],&data[hi]);   //事實是,應該加上這句,直接交換,即可。
 return i;                       //
}

    我們知道當j最後指到了r之後,是一定會有A[j]<=x的(即=),所以這個if判斷就有點多餘,沒有意義。所以應該如算法導論上的版本那般,最後直接交換swap(&data[i+1],&data[hi]);   即可,返回i+1。所以,總體說來,算法導論上的版本那樣寫,比請讀者思考版更規範,更合乎情理。ok,請接着往下閱讀。

   

    當然,上述partition過程中,也可以去掉swap函數的調用,直接寫在分割函數裏:

int partition(int data[],int lo,int hi)
{
 int i,j,t;
 int key = data[hi];   //還是以最後一個元素作爲哨兵,即主元元素
 i = lo-1;
 for (j =lo;j<=hi;j++)
  if(data[j]<key)
  {
   i++;
   t = data[j];
   data[j] = data[i];
   data[i] = t;
  }
  data[hi] = data[i+1];  //先,data[i+1]賦給data[hi]
  data[i+1] = key;       //後,把事先保存的key值,即data[hi]賦給data[i+1]
  //不可調換這倆條語句的順序。
  return i+1;
}

提醒:
1、程序中儘量不要有任何多餘的代碼。
2、你最好絕對清楚的知道,程序的某一步,是該用if,還是該用while,等任何細節的東西。

   ok,以上程序的測試用例,可以簡單編寫如下:

int main()
{
 int a[8]={3,8,7,1,2,5,6,4};
 QuickSort(a,0,N-1);
 for(int i=0;i<8;i++)
  cout<<a[i]<<endl;
 return 0;
}

    當然,如果,你如果對以上的測試用例不夠放心,可以採取1~10000的隨機數進行極限測試,保證程序的萬無一失(主函數中測試用的隨機數例子,即所謂的“極限”測試,下文會給出)。
    至於上述程序是什麼結果,相信,不用我再囉嗦。:D。

補充一種寫法:

void quickSort(int p, int q)   
{   
 if(p < q)   
 {   
  int x = a[p];    //以第一個元素爲主元
  int i = p;   
  for(int j = p+1; j < q; j++)   
  {   
   if(a[j] < x)   
   {   
    i++;   
    int temp = a[i];   
    a[i] = a[j];   
    a[j] = temp;   
   }   
  }   
  int temp = a[p];   
  a[p] = a[i];   
  a[i] = temp;   
  quickSort(p, i);   
  quickSort(i+1, q);   
 }   
}  


二、國內教材雙向掃描版
    國內教材上一般所用的通用版本,是我寫的第二篇文章中所提到的霍爾排序或其變形,而非上述所述的算法導論上的版本。而且,現在網上一般的朋友,也是更傾向於採用此種思路來實現快速排序算法。ok,請看:
          2.1、Hoare版本
    那麼,什麼是霍爾提出的快速排序版本列?如下,即是:

HOARE-PARTITION(A, p, r)
 1  x ← A[p]
 2  i ← p - 1
 3  j ← r + 1
 4  while TRUE
 5      do repeat j ← j - 1
 6           until A[j] ≤ x
 7         repeat i ← i + 1
 8           until A[i] ≥ x
 9         if i < j
10            then exchange A[i] <-> A[j]
11            else return j

     同樣,根據以上僞代碼,不難寫出以下的c/c++代碼:

  1. //此處原來的代碼有幾點錯誤,後聽從了Joshua的建議,現修改如下:  
  2. int partition(int data[],int lo,int hi)  //。  
  3. {  
  4.  int key=data[lo];  
  5.  int l=lo-1;  
  6.  int h=hi+1;  
  7.  for(;;)  
  8.  {  
  9.   do{  
  10.    h--;  
  11.   }while(data[h]>key);  
  12.   
  13.   do{  
  14.    l++;  
  15.   }while(data[l]<key);  
  16.   
  17.   if(l<h)  
  18.   {  
  19.    swap(data[l],data[h]);  
  20.   }  
  21.   else  
  22.   {  
  23.    return h;     
  24.    //各位注意了,這裏的返回值是h。不是返回各位習以爲常的樞紐元素,即不是l之類的。  
  25.   }  
  26.  }  
  27. }  
 或者原來的代碼修改成這樣(已經過測試,有誤):

int partition(int data[],int lo,int hi)  //。
{
 int key=data[lo];
 int l=lo;
 int h=hi;
 for(;;)
 {
  while(data[h]>key)   //不能加 “=”
   h--;
  while(data[l]<key)    //不能加 “=”
   l++;
  if(l<h)
  {
   swap(data[l],data[h]);
  }
  else
  {
   return h;   //各位注意了,這裏的返回值是h。不是返回各位習以爲常的樞紐元素,即不是l之類的。
  }
 }
}
 //這個版本,已經證明有誤,因爲當data[l] == data[h] == key的時候,將會進入死循環,所以淘汰。因此,使用上面的do-while 形式吧。

    讀者可以試下,對這個序列進行排序,用上述淘汰版本將立馬進入死循環:int data[16]={ 1000, 0, 6, 5, 4, 3, 2, 1, 7, 156, 44, 23, 123, 11, 5 };。

或者,如朋友顏沙所說:
如果data數組有相同元素就可能陷入死循環,比如:
      2 3 4 5 6 2 
  l->|             |<-h

交換l和h單元后重新又回到:
      2 3 4 5 6 2 
  l->|             |<-h

而第一個程序不存在這種情況,因爲它總是在l和h調整後比較,也就是l終究會大於等於h。

.

    相信,你已經看出來了,上述的第一個程序中partition過程的返回值h並不是樞紐元的位置,但是仍然保證了A[p..j] <= A[j+1...q]。
    這種方法在效率上與以下將要介紹的Hoare的幾個變形版本差別甚微,只不過是上述代碼相對更爲緊湊點而已。

       2.2、Hoare的幾個變形版本
    ok,可能,你對上述的最初的霍爾排序partition過程,理解比較費力,沒關係,我再寫幾種變形,相信,你立馬就能瞭解此雙向掃描是怎麼一回事了。

int partition(int data[],int lo,int hi)  //雙向掃描。
{
 int key=data[lo];   //以第一個元素爲主元
 int l=lo;
 int h=hi;
 while(l<h)
 {
  while(key<=data[h] && l<h) 
   h--;
  data[l]=data[h];
  while(data[l]<=key && l<h) 
   l++;
  data[h]=data[l];
 }
 data[l]=key;  //1.key。只有出現要賦值的情況,才事先保存好第一個元素的值。
 return l;     //這裏和以下所有的Hoare的變形版本都是返回的是樞紐元素,即主元元素l。
}

補充說明:同樣,還是舉上述那個例子,如下爲第一趟排序(更多詳盡的分析請參考第二篇文章):
第一趟(五步曲):
   a:3   8   7   1   2   5   6   4   //以第一個元素爲主元
        2   8   7   1       5   6   4
   b:2       7   1   8   5   6   4
   c:2   1   7       8   5   6   4
   d:2   1       7   8   5   6   4
   e:2   1   3   7   8   5   6   4   //最後補上,關鍵字3

然後,對整個數組進行遞歸排序:

void QuickSort(int data[], int lo, int hi)
{
    if (lo<hi)
    {
        int k = partition(data, lo, hi);
        QuickSort(data, lo, k-1);
        QuickSort(data, k+1, hi);
    }
}

當然,你也可以這麼寫,把遞歸過程寫在同一個排序過程裏:

void QuickSort(int data[],int lo,int hi)
{
   int i,j,temp;
   temp=data[lo];    //還是以第一個元素爲主元。
   i=lo;
   j=hi;
   if(lo>hi)
      return;
   while(i!=j)
   {
      while(data[j]>=temp && j>i)
         j--;
      if(j>i)
         data[i++]=data[j];
      while(data[i]<=temp && j>i)
         i++;
      if(j>i)
   data[j--]=data[i];     
   }
   data[i]=temp;                       //2.temp。同上,返回的是樞紐元素,即主元元素。
   QuickSort(data,lo,i-1);  //遞歸左邊
   QuickSort(data,i+1,hi);  //遞歸右邊
}

  或者,如下:

  1. void quicksort (int[] a, int lo, int hi)  
  2. {  
  3. //  lo is the lower index, hi is the upper index  
  4. //  of the region of array a that is to be sorted  
  5.     int i=lo, j=hi, h;  
  6.   
  7.     // comparison element x  
  8.     int x=a[(lo+hi)/2];  
  9.   
  10.     //  partition  
  11.     do  
  12.     {      
  13.         while (a[i]<x) i++;   
  14.         while (a[j]>x) j--;  
  15.         if (i<=j)  
  16.         {  
  17.             h=a[i]; a[i]=a[j]; a[j]=h;  
  18.             i++; j--;  
  19.         }  
  20.     } while (i<=j);  
  21.   
  22.     //  recursion  
  23.     if (lo<j) quicksort(a, lo, j);  
  24.     if (i<hi) quicksort(a, i, hi);  
  25. }  

另,本人在一本國內的數據結構教材上(,此處非指那本),看到的一種寫法,發現如下問題:一、冗餘繁雜,二、錯誤之處無所不在,除了會犯一些註釋上的錯誤,一些最基本的代碼,都會弄錯。詳情,如下:

void QuickSort(int data[],int lo,int hi)
{
 int i,j,key;
 if(lo<hi)
 {
  i=lo;
  j=hi;
  key=data[lo]; 
  //已經測試:原教材上,原句爲“data[0]=data[lo];”,有誤。
  //因爲只能用一個臨時變量key保存着主元,data[lo],而若爲以上,則相當於覆蓋原元素data[0]的值了。
        do
        {
   while(data[j]>=key&&i<j) 
    j--;        
   if(i<j)                             
   { 
    data[i]=data[j];
    //i++;  這是教材上的語句,爲使代碼簡潔,我特意去掉。
   }           
   while(data[i]<=key&&i<j)
    i++;     
   if(i<j)                       
   { 
    data[j]=data[i];
    //j--;    這是教材上的語句,爲使代碼簡潔,我特意去掉。
   }              
        }while(i!=j);
        data[i]=key;        //3.key。
  //已經測試:原教材上,原句爲“data[i]=data[0];”,有誤。
        QuickSort(data,lo,i-1);     //對標準值左半部遞歸調用本函數
        QuickSort(data,i+1,hi);    //對標準值右半部遞歸調用本函數
 }
}

    然後,你能很輕易的看到,這個寫法,與上是同一寫法,之所以寫出來,是希望各位慎看國內的教材,多多質疑+思考,勿輕信。

    ok,再給出一種取中間元素爲主元的實現:

void QuickSort(int data[],int lo,int hi)

 int pivot,l,r,temp;
 l = lo; 
 r = hi; 
 pivot=data[(lo+hi)/2]; //取中位值作爲分界值 
 while(l<r)  
 {  
  while(data[l]<pivot) 
   ++l;  
  while(data[r]>pivot) 
   --r;     
  if(l>=r) 
   break;  
  temp = data[l];  
  data[l] = data[r];  
  data[r] = temp;  
  ++l;  
  --r;  
 } 
 if(l==r) 
  l++; 
 if(lo<r) 
  QuickSort(data,lo,l-1); 
 if(l<hi) 
  QuickSort(data,r+1,hi); 
}

或者,這樣寫:

 void quickSort(int arr[], int left, int right) 
{
 int i = left, j = right;
 int tmp;
 int pivot = arr[(left + right) / 2];   //取中間元素爲主元
 
 /* partition */
 while (i <= j) 
 {
  while (arr[i] < pivot)
   i++;
  while (arr[j] > pivot)
   j--;
  if (i <= j)
  {
   tmp = arr[i];
   arr[i] = arr[j];
   arr[j] = tmp;
   i++;
   j--;
  }
 }
}

上述演示過程,如下圖所示(取中間元素爲主元,第一趟排序):

 


三、快速排序的隨機化版本
    以下是完整測試程序,由於給的註釋夠詳盡了,就再做多餘的解釋了:

//交換兩個元素值,咱們換一種方式,採取引用“&” 
void swap(int& a , int& b)
{
 int temp = a;
 a = b;
 b = temp;
}

//返回屬於[lo,hi)的隨機整數 
int rand(int lo,int hi)
{
 int size = hi-lo+1;
 return  lo+ rand()%size; 
}

//分割,換一種方式,採取指針a指向數組中第一個元素 
int RandPartition(int* data, int lo , int hi)
{    
 //普通的分割方法和隨機化分割方法的區別就在於下面三行 
 swap(data[rand(lo,hi)], data[lo]);
    int key = data[lo];
 int i = lo;
 
    for(int j=lo+1; j<=hi; j++)
 {
  if(data[j]<=key)
  {
   i = i+1;
   swap(data[i], data[j]);
  }            
 } 
 swap(data[i],data[lo]);
 return i;
}

//逐步分割排序 
void RandQuickSortMid(int* data, int lo, int hi)
{
 if(lo<hi)
 {
  int k = RandPartition(data,lo,hi);
  RandQuickSortMid(data,lo,k-1);
  RandQuickSortMid(data,k+1,hi);
 }
}
int main()
{
 const int N = 100; //此就是上文說所的“極限”測試。爲了保證程序的準確無誤,你也可以讓N=10000。
 int *data = new int[N];      
    for(int i =0; i<N; i++)
  data[i] = rand();   //同樣,隨機化的版本,採取隨機輸入。 
    for(i=0; i<N; i++)
  cout<<data[i]<<" ";
    RandQuickSortMid(data,0,N-1);
 cout<<endl;
 for(i=0; i<N; i++)
  cout<<data[i]<<" ";
 cout<<endl;
    return 0;
}

 
四、三數取中分割法

    我想,如果你愛思考,可能你已經在想一個問題了,那就是,像上面的程序版本,其中算法導論上採取單向掃描中,是以最後一個元素爲樞紐元素,即主元,而在Hoare版本及其幾個變形中,都是以第一個元素、或中間元素爲主元,最後,上述給的快速排序算法的隨機化版本,則是以序列中任一一個元素作爲主元。
    那麼,樞紐元素的選取,即主元元素的選取是否決定快速排序最終的效率列?
    
    答案是肯定的,當我們採取data[lo],data[mid],data[hi]三者之中的那個第二大的元素爲主元時,便能盡最大限度保證快速排序算法不會出現O(N^2)的最壞情況。這就是所謂的三數取中分割方法。當然,針對的還是那個Partition過程。

    ok,直接寫代碼:

//三數取中分割方法 
int RandPartition(int* a, int p , int q)
{    
 //三數取中方法的關鍵就在於下述六行,
 int m=(p+q)/2;
 if(a[p]<a[m])
  swap(a[p],a[m]);
 if(a[q]<a[m])
  swap(a[q],a[m]);
 if(a[q]<a[p])
  swap(a[q],a[p]);
 int key = a[p];
 int i = p;
 
 for(int j = p+1; j <= q; j++)
 {
  if(a[j] <= key)
  {
   i = i+1;
   if(i != j) 
    swap(a[i], a[j]);                 
  }            
 } 
 
 swap(a[i],a[p]);  
 return i;
}

void QuickSort(int data[], int lo, int hi)
{
    if (lo<hi)
    {
        int k = RandPartition(data, lo, hi);
        QuickSort(data, lo, k-1);
        QuickSort(data, k+1, hi);
    }
}

    經過測試,這種方法可行且有效,不過到底其性能、效率有多好,還有待日後進一步的測試。


第二部分、快速排序的非遞歸版
    ok,相信,您已經看到,上述所有的快速排序算法,都是遞歸版本的,那還有什麼辦法可以實現此快速排序算法列?對了,遞歸,與之相對的,就是非遞歸了。
    以下,就是快速排序算法的非遞歸實現:

  template <class T>
int RandPartition(T data[],int lo,int hi)
{
 T v=data[lo];
 while(lo<hi)
 {  
  while(lo<hi && data[hi]>=v) 
   hi--;
  data[lo]=data[hi];
  while(lo<hi && data[lo]<=v) 
   lo++;
  data[hi]=data[lo];
 }
 data[lo]=v;
 return lo; 
}

//快速排序的非遞歸算法
template <class T>
void QuickSort(T data[],int lo,int hi)
{
 stack<int> st;
 int key;
 do{
  while(lo<hi)
  {
   key=partition(data,lo,hi);   
   //遞歸的本質是什麼?對了,就是藉助棧,進棧,出棧來實現的。
   if( (key-lo)<(key-hi) )
   {
    st.push(key+1);    
    st.push(hi);
    hi=key-1;
   }
   else
   {
    st.push(lo);
    st.push(key-1);
    lo=key+1;
   }   
  }
  if(st.empty()) 
   return;
  hi=st.top();
  st.pop();  
  lo=st.top();
  st.pop();  
 }while(1);
}

void QuickSort(int data[], int lo, int hi)
{
    if (lo<hi)
    {
        int k = RandPartition(data, lo, hi);
        QuickSort(data, lo, k-1);
        QuickSort(data, k+1, hi);
    }
}

    如果你還尚不知道快速排序算法的原理與算法思想,請參考本人寫的關於快速排序算法的前倆篇文章:一之續、快速排序算法的深入分析,及一、快速排序算法。如果您看完了此篇文章後,還是不知如何從頭實現快速排序算法,那麼好吧,伸出手指,數數,1,2,3,4,5....數到100之後,再來看此文。

    -------------------------------------------------------------
    據本文評論裏頭網友ybt631的建議,表示非常感謝,並補充闡述下所謂的並行快速排序

    Intel Threading Building Blocks(簡稱TBB)是一個C++的並行編程模板庫,它能使你的程序充分利用多核CPU的性能優勢,方便使用,效率很高。
    以下是,parallel_sort.h頭文件中的關鍵代碼:

  1. 00039 template<typename RandomAccessIterator, typename Compare>  
  2. 00040 class quick_sort_range: private no_assign {  
  3. 00041   
  4. 00042     inline size_t median_of_three(const RandomAccessIterator &array, size_t l, size_t m, size_t r) const {  
  5. 00043         return comp(array[l], array[m]) ? ( comp(array[m], array[r]) ? m : ( comp( array[l], array[r]) ? r : l ) )   
  6. 00044                                         : ( comp(array[r], array[m]) ? m : ( comp( array[r], array[l] ) ? r : l ) );  
  7. 00045     }  
  8. 00046   
  9. 00047     inline size_t pseudo_median_of_nine( const RandomAccessIterator &array, const quick_sort_range &range ) const {  
  10. 00048         size_t offset = range.size/8u;  
  11. 00049         return median_of_three(array,   
  12. 00050                                median_of_three(array, 0, offset, offset*2),  
  13. 00051                                median_of_three(array, offset*3, offset*4, offset*5),  
  14. 00052                                median_of_three(array, offset*6, offset*7, range.size - 1) );  
  15. 00053   
  16. 00054     }  
  17. 00055   
  18. 00056 public:  
  19. 00057   
  20. 00058     static const size_t grainsize = 500;  
  21. 00059     const Compare &comp;  
  22. 00060     RandomAccessIterator begin;  
  23. 00061     size_t size;  
  24. 00062   
  25. 00063     quick_sort_range( RandomAccessIterator begin_, size_t size_, const Compare &comp_ ) :  
  26. 00064         comp(comp_), begin(begin_), size(size_) {}  
  27. 00065   
  28. 00066     bool empty() const {return size==0;}  
  29. 00067     bool is_divisible() const {return size>=grainsize;}  
  30. 00068   
  31. 00069     quick_sort_range( quick_sort_range& range, split ) : comp(range.comp) {  
  32. 00070         RandomAccessIterator array = range.begin;  
  33. 00071         RandomAccessIterator key0 = range.begin;   
  34. 00072         size_t m = pseudo_median_of_nine(array, range);  
  35. 00073         if (m) std::swap ( array[0], array[m] );  
  36. 00074   
  37. 00075         size_t i=0;  
  38. 00076         size_t j=range.size;  
  39. 00077         // Partition interval [i+1,j-1] with key *key0.  
  40. 00078         for(;;) {  
  41. 00079             __TBB_ASSERT( i<j, NULL );  
  42. 00080             // Loop must terminate since array[l]==*key0.  
  43. 00081             do {  
  44. 00082                 --j;  
  45. 00083                 __TBB_ASSERT( i<=j, "bad ordering relation?" );  
  46. 00084             } while( comp( *key0, array[j] ));  
  47. 00085             do {  
  48. 00086                 __TBB_ASSERT( i<=j, NULL );  
  49. 00087                 if( i==j ) goto partition;  
  50. 00088                 ++i;  
  51. 00089             } while( comp( array[i],*key0 ));  
  52. 00090             if( i==j ) goto partition;  
  53. 00091             std::swap( array[i], array[j] );  
  54. 00092         }  
  55. 00093 partition:  
  56. 00094         // Put the partition key were it belongs  
  57. 00095         std::swap( array[j], *key0 );  
  58. 00096         // array[l..j) is less or equal to key.  
  59. 00097         // array(j..r) is greater or equal to key.  
  60. 00098         // array[j] is equal to key  
  61. 00099         i=j+1;  
  62. 00100         begin = array+i;  
  63. 00101         size = range.size-i;  
  64. 00102         range.size = j;  
  65. 00103     }  
  66. 00104 };  
  67. 00105   
  68. ....  
  69. 00218 #endif  

    再貼一下插入排序、快速排序之其中的倆種版本、及插入排序與快速排序結合運用的實現代碼,如下:

  1.  /// 插入排序,最壞情況下爲O(n^2)  
  2. templatetypename InPos, typename ValueType >  
  3. void _isort( InPos posBegin_, InPos posEnd_, ValueType* )  
  4. {  
  5. /**************************************************************************** 
  6. *    僞代碼如下: 
  7. *        for i = [1, n) 
  8. *            t = x 
  9. *            for( j = i; j > 0 && x[j-1] > t; j-- ) 
  10. *                x[j] = x[j-1] 
  11. *            x[j] = x[j-1] 
  12.  ****************************************************************************/  
  13.  if( posBegin_ == posEnd_ )  
  14.  {  
  15.   return;  
  16.  }  
  17.    
  18.  /// 循環迭代,將每個元素插入到合適的位置  
  19.  for( InPos pos = posBegin_; pos != posEnd_; ++pos )  
  20.  {  
  21.   ValueType Val = *pos;  
  22.   InPos posPrev = pos;  
  23.   InPos pos2 = pos;  
  24.   /// 當元素比前一個元素大時,交換  
  25.   for( ;pos2 != posBegin_ && *(--posPrev) > Val ; --pos2 )  
  26.   {  
  27.    *pos2 = *posPrev;  
  28.   }  
  29.   *pos2 = Val;  
  30.  }  
  31. }  
  32.   
  33. /// 快速排序1,平均情況下需要O(nlogn)的時間  
  34. templatetypename InPos >  
  35. inline void qsort1( InPos posBegin_, InPos posEnd_ )  
  36. {  
  37. /**************************************************************************** 
  38. *    僞代碼如下: 
  39. *        void qsort(l, n) 
  40. *            if(l >= u) 
  41. *                return; 
  42. *            m = l 
  43. *            for i = [l+1, u] 
  44. *                if( x < x[l] 
  45. *                    swap(++m, i) 
  46. *            swap(l, m) 
  47. *            qsort(l, m-1) 
  48. *            qsort(m+1, u) 
  49.  ****************************************************************************/  
  50.  if( posBegin_ == posEnd_ )  
  51.  {  
  52.   return;  
  53.  }  
  54.    
  55.  /// 將比第一個元素小的元素移至前半部  
  56.  InPos pos = posBegin_;  
  57.  InPos posLess = posBegin_;  
  58.  for( ++pos; pos != posEnd_; ++pos )  
  59.  {  
  60.   if( *pos < *posBegin_ )  
  61.   {  
  62.    swap( *pos, *(++posLess) );  
  63.   }  
  64.  }  
  65.    
  66.  /// 把第一個元素插到兩快元素中央  
  67.  swap( *posBegin_, *(posLess) );  
  68.    
  69.  /// 對前半部、後半部執行快速排序  
  70.  qsort1(posBegin_, posLess);  
  71.  qsort1(++posLess, posEnd_);  
  72. };  
  73.   
  74. /// 快速排序2,原理與1基本相同,通過兩端同時迭代加快平均速度  
  75. template<typename InPos>  
  76. void qsort2( InPos posBegin_, InPos posEnd_ )  
  77. {  
  78.  if( distance(posBegin_, posEnd_) <= 0 )  
  79.  {  
  80.   return;  
  81.  }  
  82.    
  83.  InPos posL = posBegin_;  
  84.  InPos posR = posEnd_;  
  85.    
  86.  whiletrue )  
  87.  {  
  88.   /// 找到不小於第一個元素的數  
  89.   do  
  90.   {  
  91.    ++posL;  
  92.   }while( *posL < *posBegin_ && posL != posEnd_ );  
  93.     
  94.   /// 找到不大於第一個元素的數  
  95.   do   
  96.   {  
  97.    --posR;  
  98.   } while ( *posR > *posBegin_ );  
  99.     
  100.   /// 兩個區域交叉時跳出循環  
  101.   if( distance(posL, posR) <= 0 )  
  102.   {  
  103.    break;  
  104.   }  
  105.   /// 交換找到的元素  
  106.   swap(*posL, *posR);  
  107.  }  
  108.    
  109.  /// 將第一個元素換到合適的位置  
  110.  swap(*posBegin_, *posR);  
  111.  /// 對前半部、後半部執行快速排序2  
  112.  qsort2(posBegin_, posR);  
  113.  qsort2(++posR, posEnd_);  
  114. }  
  115.   
  116. /// 當元素個數小與g_iSortMax時使用插入排序,g_iSortMax是根據STL庫選取的  
  117. const int g_iSortMax = 32;  
  118. /// 該排序算法是快速排序與插入排序的結合  
  119. template<typename InPos>  
  120. void qsort3( InPos posBegin_, InPos posEnd_ )  
  121. {  
  122.  if( distance(posBegin_, posEnd_) <= 0 )  
  123.  {  
  124.   return;  
  125.  }  
  126.    
  127.  /// 小與g_iSortMax時使用插入排序  
  128.  if( distance(posBegin_, posEnd_) <= g_iSortMax )  
  129.  {  
  130.   return isort(posBegin_, posEnd_);  
  131.  }  
  132.    
  133.  /// 大與g_iSortMax時使用快速排序  
  134.  InPos posL = posBegin_;  
  135.  InPos posR = posEnd_;  
  136.    
  137.  whiletrue )  
  138.  {  
  139.   do  
  140.   {  
  141.    ++posL;  
  142.   }while( *posL < *posBegin_ && posL != posEnd_ );  
  143.     
  144.   do   
  145.   {  
  146.    --posR;  
  147.   } while ( *posR > *posBegin_ );  
  148.     
  149.   if( distance(posL, posR) <= 0 )  
  150.   {  
  151.    break;  
  152.   }  
  153.   swap(*posL, *posR);  
  154.  }  
  155.  swap(*posBegin_, *posR);  
  156.  qsort3(posBegin_, posR);  
  157.  qsort3(++posR, posEnd_);  
  158. }  

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