標籤:style color os io strong 檔案 資料 ar
1、演算法思想
快速排序是C.R.A.Hoare於1962年提出的一種劃分交換排序。它採用了一種分治的策略,通常稱其為分治法(Divide-and-ConquerMethod)。
(1) 分治法的基本思想
分治法的基本思想是:將原問題分解為若干個規模更小但結構與原問題相似的子問題。遞迴地解這些子問題,然後將這些子問題的解組合為原問題的解。
(2)快速排序的基本思想
設當前待排序的無序區為R[low..high],利用分治法可將快速排序的基本思想描述為:
①分解:
在R[low..high]中任選一個記錄作為基準(Pivot),以此基準將當前無序區劃分為左、右兩個較小的子區間R[low..pivotpos-1)和R[pivotpos+1..high],並使左邊子區間中所有記錄的關鍵字均小於等於基準記錄(不妨記為pivot)的關鍵字pivot.key,右邊的子區間中所有記錄的關鍵字均大於等於pivot.key,而基準記錄pivot則位於正確的位置(pivotpos)上,它無須參加後續的排序。
注意:
劃分的關鍵是要求出基準記錄所在的位置pivotpos。劃分的結果可以簡單地表示為(注意pivot=R[pivotpos]):
R[low..pivotpos-1].keys≤R[pivotpos].key≤R[pivotpos+1..high].keys
其中low≤pivotpos≤high。
②求解:
通過遞迴調用快速排序對左、右子區間R[low..pivotpos-1]和R[pivotpos+1..high]快速排序。
③組合:
因為當"求解"步驟中的兩個遞迴調用結束時,其左、右兩個子區間已有序。對快速排序而言,"組合"步驟無須做什麼,可看作是空操作。
2、快速排序演算法QuickSort
(1)遞迴演算法
註:下面的演算法是經過了很多的考慮之後達到了一個非常完美的解決方案,比很多書上寫的要完整,主要是考慮了數組越界的情況
template<class T>
void QuickSort(T a[],int left,int right)
{
if (left >= right)
return;
int l = left+1,
r = right;
int pivot = a[left];
while (l < r)
{
while (l < r&&a[l] <= pivot)//為防止數組越界,必須加上限制條件l < r
++l;
while (r > left&&a[r] >= pivot)//同上防止數組越界,須加上限制條件r>left
--r;
if (l < r)
{
T tmp = a[l];
a[l] = a[r];
a[r] = tmp;
}
}
if (a[r] < pivot)//此條件很重要,當l和r相等時,我們需要判斷是否需要交換
{
a[left] = a[r];
a[r] = pivot;
}
QuickSort(a,left,r-1);
QuickSort(a,r+1,right);
}
(2)非遞迴的解決方案
template<class T>
struct sPoint
{
T* p;
int index;
}
template<class T>
void QuickSort2(T a[],int n)
{
sPoint<T> sp;
stack<sPoint<T> > ss;
while (1)
{
if (2 == n)
{
if (a[0] > a[1])
Swap(a[0],a[1]);
if (ss.empty())
break;
sp = ss.top();
ss.pop();
a = sp.p;
n = sp.index;
continue;
}
T* l = a+1;
T* r = a+n-1;
while (l < r)
{
while (l < r&&*r <= *a)
++l;
while (r > a&&*r >= *a)
--r;
if (l < r)
Swap(*l,*r);
}
if (*r < *a)
Swap(*r,*a);
sp.p = r+1;
sp.index = n-(r-a+1);
ss.push(sp);
n = r-a;
}
}
3、演算法分析
快速排序的時間主要耗費在劃分操作上,對長度為k的區間進行劃分,共需k-1次關鍵字的比較。
(1)最壞時間複雜度
最壞情況是每次劃分選取的基準都是當前無序區中關鍵字最小(或最大)的記錄,劃分的結果是基準左邊的子區間為空白(或右邊的子區間為空白),而劃分所得的另一個非空的子區間中記錄數目,僅僅比劃分前的無序區中記錄個數減少一個。
因此,快速排序必須做n-1次劃分,第i次劃分開始時區間長度為n-i+1,所需的比較次數為n-i(1≤i≤n-1),故總的比較次數達到最大值:
Cmax = n(n-1)/2=O(n2)
如果按上面給出的劃分演算法,每次取當前無序區的第1個記錄為基準,那麼當檔案的記錄已按遞增序(或遞減序)排列時,每次劃分所取的基準就是當前無序區中關鍵字最小(或最大)的記錄,則快速排序所需的比較次數反而最多。
(2) 最好時間複雜度
在最好情況下,每次劃分所取的基準都是當前無序區的"中值"記錄,劃分的結果是基準的左、右兩個無序子區間的長度大致相等。總的關鍵字比較次數:
0(nlgn)
注意:
用遞迴樹來分析最好情況下的比較次數更簡單。因為每次劃分後左、右子區間長度大致相等,故遞迴樹的高度為O(lgn),而遞迴樹每一層上各結點所對應的劃分過程中所需要的關鍵字比較次數總和不超過n,故整個排序過程所需要的關鍵字比較總次數C(n)=O(nlgn)。
因為快速排序的記錄移動次數不大於比較的次數,所以快速排序的最壞時間複雜度應為0(n2),最好時間複雜度為O(nlgn)。
(3)基準關鍵字的選取
在當前無序區中選取劃分的基準關鍵字是決定演算法效能的關鍵。
①"三者取中"的規則
"三者取中"規則,即在當前區間裡,將該區間首、尾和中間位置上的關鍵字比較,取三者之中值所對應的記錄作為基準,在劃分開始前將該基準記錄和該區伺的第1個記錄進行交換,此後的劃分過程與上面所給的Partition演算法完全相同。
②取位於low和high之間的隨機數k(low≤k≤high),用R[k]作為基準
選取基準最好的方法是用一個隨機函數產生一個取位於low和high之間的隨機數k(low≤k≤high),用R[k]作為基準,這相當於強迫R[low..high]中的記錄是隨機分布的。用此方法所得到的快速排序一般稱為隨機的快速排序演算法。
注意:
隨機化的快速排序與一般的快速排序演算法差別很小。但隨機化後,演算法的效能大大地提高了,尤其是對初始有序的檔案,一般不可能導致最壞情況的發生。演算法的隨機化不僅僅適用於快速排序,也適用於其它需要資料隨機分布的演算法。
(4)平均時間複雜度
儘管快速排序的最壞時間為O(n2),但就平均效能而言,它是基於關鍵字比較的內部排序演算法中速度最快者,快速排序亦因此而得名。它的平均時間複雜度為O(nlgn)。
(5)空間複雜度
快速排序在系統內部需要一個棧來實現遞迴。若每次劃分較為均勻,則其遞迴樹的高度為O(lgn),故遞迴後需棧空間為O(lgn)。最壞情況下,遞迴樹的高度為O(n),所需的棧空間為O(n)。
(6)穩定性
快速排序是非穩定的。