1. 程式人生 > >排序演算法大雜燴之快速排序

排序演算法大雜燴之快速排序

排序演算法大雜燴主幹文章傳送門

快速排序

  • 接著主文章結構繼續分析快速排序
  • 快速排序實現主流上分為兩種,第一種為快排的開山鼻祖 C . A . R . H
    o a r e C. A. R. Hoare
    在1962年給出的,第二種是後來者給出的另一種實現方案。本文給出兩種並非最原始的,而是經過隨機化主元后給出的改進方案。
#include <iostream>
#include
<vector>
#include <cstdlib> #include <ctime> using namespace std; void swap(int &a,int &b){int c = a;a = b;b = c;} int partition(vector<int> &a,int low,int high) { //C. A. R. Hoare srand(unsigned(time(NULL))); int j = low + rand()%(high - low + 1); swap
(a[high],a[j]); j = low; for(int i = low;i < high;i ++){ if(a[i] <= a[high])swap(a[i],a[j++]); } swap(a[j ],a[high]); return j ; } int partition2(vector<int>&a,int low,int high) { srand(unsigned(time(NULL))); int j = low + rand()%(high - low + 1); swap(a[high],a[j]); int pivot = a[high]; while(low < high){ while(low < high && a[low] <= pivot){low ++;} a[high] = a[low]; while(low < high && a[high] > pivot){high --;} a[low] = a[high]; } cout << low << " " << high << ";\n"; a[low] = pivot; return low; } void quickSort(vector<int> &a,int low,int high) { if(low >= high) return ; int pivot = partition(a,low,high); quickSort(a,low,pivot-1); quickSort(a,pivot+1,high); } int main() { vector<int> a = {1,40,10,4,5,8,9,1}; // cout << partition2(a,0,a.size()-1) << "|"; quickSort(a,0,a.size()-1); for(auto c : a) cout << c << " "; return 0; }
  • 思路引擎

  • 快速排序是利用分治策略完成,分成三部曲:分解,解決,合併
    問題定義:將序列 A [ 1.. n ] A[1..n] 非降序
    分解:將序列 A [ l . . h ] A[l..h] 找到r,並使得 A [ l . . r 1 ] A[l..r-1] 的元素均小於 A [ r ] A[r] A [ r + 1.. h ] A[r+1..h] 均大於 A [ r ] A[r]
    解決:遞迴的呼叫快速排序演算法,對 A [ l . . r 1 ] A[l..r-1] A [ r + 1.. h ] A[r+1..h] 在進行排序
    合併:所有子陣列已經有序,無需進行合併操作。

  • 如何分解子陣列,使得滿足分治的目的
    方法一:

    1. 空出首元素(首元素作為主元), h h l l 端向序列中間進行掃描;
    2. 對於 h h 端元素,如果該元素小於 A [ r ] A[r] 填補 l l 端空缺位,那麼該元素所佔據的位置就成了空缺位,然後切換到 l l 端;
    3. 對於 l l 端元素,如果該元素大於 A [ r ] A[r] 填補 l l 端空缺位,那麼該元素所佔據的位置就成了空缺位,然後切換到 h h 端;
    4. l l 端與 l l 端相遇,那麼相遇點一定是空缺位,再用主元元素填補就可以了。

    方法二:

    1. 設定兩個變數i、j,排序開始的時候:i=0,j=N-1;
    2. 以第一個陣列元素作為關鍵資料,賦值給key,即key=A[0];
    3. 從j開始向前搜尋,即由後開始向前搜尋(j- -),找到第一個小於key的值A[j],將A[j]和A[i]互換;3. 從j開始向前搜尋,即由後開始向前搜尋(j- -),找到第一個小於key的值A[j],將A[j]和A[i]互換;
    4. 從i開始向後搜尋,即由前開始向後搜尋(i++),找到第一個大於key的A[i],將A[i]和A[j]互換;
    5. 重複第3、4步,直到i=j; (3,4步中,沒找到符合條件的值,即3中A[j]不小於key,4中A[i]不大於key的時候改變j、i的值,使得j=j-1,i=i+1,直至找到為止。找到符合條件的值,進行交換的時候i, j指標位置不變。另外,i==j這一過程一定正好是i+或j-完成的時候,此時令迴圈結束)。

    方法二的描述引用自 百度百科-快速排序

  • 時間複雜度

    1. 快速排序的時間複雜度與陣列劃分的效果有關,下面給出兩個極端下的時間複雜度求解

      • 當每次陣列劃分都只純粹劃分成長度為1和長度為 h l h-l 的兩個部分,時間複雜度
        T ( n ) = T ( n 1 ) + o ( n ) T ( n ) = o ( n 2 ) T(n) = T(n-1) + o(n) \\ \Rightarrow T(n) = o(n^2)
      • 當每次陣列劃分是都能劃分成兩個相等的部分,則時間複雜度可表示
        T ( n ) = 2 T ( n 1 ) + o ( n ) T ( n ) = o ( n l o g n ) T(n) = 2T(n-1) + o(n) \\ \Rightarrow T(n) = o(nlogn)
        這裡由遞迴式求時間複雜度採用的主方法,感興趣的可移步。
    2. 由於上面的分析可以看出資料的分佈對快排的影響很大,為了儘量減少這方面的影響,我們在選取主元的時候儘量隨機選擇,而不是機械的選擇最首或者最尾。這樣快排的期望時間複雜度就變成了 o ( n l g n ) o(nlgn) ,具體的證明可以參考演算法導論的相關內容。

  • 穩定性分析
    這邊很容易可以看出快排是不穩定,原因和選擇排序比較類似,我們在進行置換元素時,無法判定置換後的位置前方是否存在鍵值相同元素,換句話說在置換時無法確保相同鍵值元素的相對次序。