作者: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++代碼:
- //此處原來的代碼有幾點錯誤,後聽從了Joshua的建議,現修改如下:
- int partition(int data[],int lo,int hi) //。
- {
- int key=data[lo];
- int l=lo-1;
- int h=hi+1;
- for(;;)
- {
- do{
- h--;
- }while(data[h]>key);
- do{
- l++;
- }while(data[l]<key);
- if(l<h)
- {
- swap(data[l],data[h]);
- }
- else
- {
- return h;
- //各位注意了,這裏的返回值是h。不是返回各位習以爲常的樞紐元素,即不是l之類的。
- }
- }
- }
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); //遞歸右邊
}
或者,如下:
- void quicksort (int[] a, int lo, int hi)
- {
- // lo is the lower index, hi is the upper index
- // of the region of array a that is to be sorted
- int i=lo, j=hi, h;
- // comparison element x
- int x=a[(lo+hi)/2];
- // partition
- do
- {
- while (a[i]<x) i++;
- while (a[j]>x) j--;
- if (i<=j)
- {
- h=a[i]; a[i]=a[j]; a[j]=h;
- i++; j--;
- }
- } while (i<=j);
- // recursion
- if (lo<j) quicksort(a, lo, j);
- if (i<hi) quicksort(a, i, hi);
- }
另,本人在一本國內的數據結構教材上(注,此處非指嚴那本),看到的一種寫法,發現如下問題:一、冗餘繁雜,二、錯誤之處無所不在,除了會犯一些註釋上的錯誤,一些最基本的代碼,都會弄錯。詳情,如下:
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頭文件中的關鍵代碼:
- 00039 template<typename RandomAccessIterator, typename Compare>
- 00040 class quick_sort_range: private no_assign {
- 00041
- 00042 inline size_t median_of_three(const RandomAccessIterator &array, size_t l, size_t m, size_t r) const {
- 00043 return comp(array[l], array[m]) ? ( comp(array[m], array[r]) ? m : ( comp( array[l], array[r]) ? r : l ) )
- 00044 : ( comp(array[r], array[m]) ? m : ( comp( array[r], array[l] ) ? r : l ) );
- 00045 }
- 00046
- 00047 inline size_t pseudo_median_of_nine( const RandomAccessIterator &array, const quick_sort_range &range ) const {
- 00048 size_t offset = range.size/8u;
- 00049 return median_of_three(array,
- 00050 median_of_three(array, 0, offset, offset*2),
- 00051 median_of_three(array, offset*3, offset*4, offset*5),
- 00052 median_of_three(array, offset*6, offset*7, range.size - 1) );
- 00053
- 00054 }
- 00055
- 00056 public:
- 00057
- 00058 static const size_t grainsize = 500;
- 00059 const Compare ∁
- 00060 RandomAccessIterator begin;
- 00061 size_t size;
- 00062
- 00063 quick_sort_range( RandomAccessIterator begin_, size_t size_, const Compare &comp_ ) :
- 00064 comp(comp_), begin(begin_), size(size_) {}
- 00065
- 00066 bool empty() const {return size==0;}
- 00067 bool is_divisible() const {return size>=grainsize;}
- 00068
- 00069 quick_sort_range( quick_sort_range& range, split ) : comp(range.comp) {
- 00070 RandomAccessIterator array = range.begin;
- 00071 RandomAccessIterator key0 = range.begin;
- 00072 size_t m = pseudo_median_of_nine(array, range);
- 00073 if (m) std::swap ( array[0], array[m] );
- 00074
- 00075 size_t i=0;
- 00076 size_t j=range.size;
- 00077 // Partition interval [i+1,j-1] with key *key0.
- 00078 for(;;) {
- 00079 __TBB_ASSERT( i<j, NULL );
- 00080 // Loop must terminate since array[l]==*key0.
- 00081 do {
- 00082 --j;
- 00083 __TBB_ASSERT( i<=j, "bad ordering relation?" );
- 00084 } while( comp( *key0, array[j] ));
- 00085 do {
- 00086 __TBB_ASSERT( i<=j, NULL );
- 00087 if( i==j ) goto partition;
- 00088 ++i;
- 00089 } while( comp( array[i],*key0 ));
- 00090 if( i==j ) goto partition;
- 00091 std::swap( array[i], array[j] );
- 00092 }
- 00093 partition:
- 00094 // Put the partition key were it belongs
- 00095 std::swap( array[j], *key0 );
- 00096 // array[l..j) is less or equal to key.
- 00097 // array(j..r) is greater or equal to key.
- 00098 // array[j] is equal to key
- 00099 i=j+1;
- 00100 begin = array+i;
- 00101 size = range.size-i;
- 00102 range.size = j;
- 00103 }
- 00104 };
- 00105
- ....
- 00218 #endif
再貼一下插入排序、快速排序之其中的倆種版本、及插入排序與快速排序結合運用的實現代碼,如下:
- /// 插入排序,最壞情況下爲O(n^2)
- template< typename InPos, typename ValueType >
- void _isort( InPos posBegin_, InPos posEnd_, ValueType* )
- {
- /****************************************************************************
- * 僞代碼如下:
- * for i = [1, n)
- * t = x
- * for( j = i; j > 0 && x[j-1] > t; j-- )
- * x[j] = x[j-1]
- * x[j] = x[j-1]
- ****************************************************************************/
- if( posBegin_ == posEnd_ )
- {
- return;
- }
- /// 循環迭代,將每個元素插入到合適的位置
- for( InPos pos = posBegin_; pos != posEnd_; ++pos )
- {
- ValueType Val = *pos;
- InPos posPrev = pos;
- InPos pos2 = pos;
- /// 當元素比前一個元素大時,交換
- for( ;pos2 != posBegin_ && *(--posPrev) > Val ; --pos2 )
- {
- *pos2 = *posPrev;
- }
- *pos2 = Val;
- }
- }
- /// 快速排序1,平均情況下需要O(nlogn)的時間
- template< typename InPos >
- inline void qsort1( InPos posBegin_, InPos posEnd_ )
- {
- /****************************************************************************
- * 僞代碼如下:
- * void qsort(l, n)
- * if(l >= u)
- * return;
- * m = l
- * for i = [l+1, u]
- * if( x < x[l]
- * swap(++m, i)
- * swap(l, m)
- * qsort(l, m-1)
- * qsort(m+1, u)
- ****************************************************************************/
- if( posBegin_ == posEnd_ )
- {
- return;
- }
- /// 將比第一個元素小的元素移至前半部
- InPos pos = posBegin_;
- InPos posLess = posBegin_;
- for( ++pos; pos != posEnd_; ++pos )
- {
- if( *pos < *posBegin_ )
- {
- swap( *pos, *(++posLess) );
- }
- }
- /// 把第一個元素插到兩快元素中央
- swap( *posBegin_, *(posLess) );
- /// 對前半部、後半部執行快速排序
- qsort1(posBegin_, posLess);
- qsort1(++posLess, posEnd_);
- };
- /// 快速排序2,原理與1基本相同,通過兩端同時迭代加快平均速度
- template<typename InPos>
- void qsort2( InPos posBegin_, InPos posEnd_ )
- {
- if( distance(posBegin_, posEnd_) <= 0 )
- {
- return;
- }
- InPos posL = posBegin_;
- InPos posR = posEnd_;
- while( true )
- {
- /// 找到不小於第一個元素的數
- do
- {
- ++posL;
- }while( *posL < *posBegin_ && posL != posEnd_ );
- /// 找到不大於第一個元素的數
- do
- {
- --posR;
- } while ( *posR > *posBegin_ );
- /// 兩個區域交叉時跳出循環
- if( distance(posL, posR) <= 0 )
- {
- break;
- }
- /// 交換找到的元素
- swap(*posL, *posR);
- }
- /// 將第一個元素換到合適的位置
- swap(*posBegin_, *posR);
- /// 對前半部、後半部執行快速排序2
- qsort2(posBegin_, posR);
- qsort2(++posR, posEnd_);
- }
- /// 當元素個數小與g_iSortMax時使用插入排序,g_iSortMax是根據STL庫選取的
- const int g_iSortMax = 32;
- /// 該排序算法是快速排序與插入排序的結合
- template<typename InPos>
- void qsort3( InPos posBegin_, InPos posEnd_ )
- {
- if( distance(posBegin_, posEnd_) <= 0 )
- {
- return;
- }
- /// 小與g_iSortMax時使用插入排序
- if( distance(posBegin_, posEnd_) <= g_iSortMax )
- {
- return isort(posBegin_, posEnd_);
- }
- /// 大與g_iSortMax時使用快速排序
- InPos posL = posBegin_;
- InPos posR = posEnd_;
- while( true )
- {
- do
- {
- ++posL;
- }while( *posL < *posBegin_ && posL != posEnd_ );
- do
- {
- --posR;
- } while ( *posR > *posBegin_ );
- if( distance(posL, posR) <= 0 )
- {
- break;
- }
- swap(*posL, *posR);
- }
- swap(*posBegin_, *posR);
- qsort3(posBegin_, posR);
- qsort3(++posR, posEnd_);
- }