《算法》C++代碼 快速排序


       快速排序,簡稱快排,常稱QuickSort、QSort。在排序算法中非常常用,其編程復雜度低,時間復雜度O(NlogN),空間復雜度O(N),執行效率穩定,而且常數很低。

       基本思想就是二分,例如你要將N個數排序,你調用了QSort(1,N)。那么快排會這樣做:

1、找出一個數x

2、將N個數分成兩部分,左邊的都比x小,右邊的都比x大

3、分別對兩邊調用QSort

       很顯然,這是二分,遞歸實現。

  先說第二步,代碼別寫得太難看,時間復雜度就是O(N),掃一遍就可以了。於是,重點便是第一步——我們假設你找x的時間是O(1),那么如果你的x每次找到的都是中位數,那么算法時間就是O(NlogN);如果你的x每次找到的都是最邊上的數(以至於你將N個數分成了1個和N-1個),那么算法時間就是O(N²)。因此,只有在優秀的選x方法下,快排才能保證O(NlogN)的復雜度。

 

  我們來詳細討論一下第一步(下面分析“中位數”的實際含義,以及給出兩種常見實現取法,高手可以跳過直接去看代碼了~)。

  我們理想情況是找中位數,但是你不可能真正去找中位數,因為那樣的時間是O(N)。新手很頭疼,“這咋整?”方法很簡單:隨便選一個就好了。

  新手更頭疼了……“你隨便選一個,選的時間當然是O(1)了,可你憑什么保證算法復雜度不退化?”其實,我也不能保證算法不退化,但我知道從概率上說,我每次都隨機選,大部分時候都沒退化多少,結果就只有很低很低的概率退化成O(N²)。新手很鄙視,“如果我完全可以給你構造出一組數據,讓你每次都選邊上的啊!這樣不就退化成O(N²)了嗎? ”這點其實是不可能的。因為我不是固定選某個位置的數,而是隨機選,所以你根本無法構造,我退化多少,只取決於概率。

  新手要放棄了……“你這快排復雜度直接取決於概率,可我概統沒學好,也不知道快排的退化概率是多少,我怎么敢用啊!萬一我用的時候正好退化了咋辦!”這點,便是我今天要重點和廣大新手說的,你們接觸到了一個算法中很重要的概念:隨機算法

  算法復雜度,只是對算法一個很粗的描述。你知道一個算法的復雜度是O(N²),其實你只是知道它是兩階而已,根本不知道真正的復雜度。復雜度的常數是多少?是3N²、0.3N²,還是1.3N²?平時我們不分析,是因為我們都按照最大復雜度分析的。題目給你N=1000,你知道算法復雜度O(N²),又知道常數很大時(例如100)程序不到1s可以運行完,於是你便敢寫了。可是現在不行了,算法是隨機的,好的時候O(NlogN)常數還很小,壞的時候O(N²)常數還很大,你還敢不分析?

  可能有人不敢用,覺得只要是概率就不能保證沒問題,萬一考試碰上就慘了。這種思想一般都是新手才會有,請你務必說服自己!我的理由很簡單,概率太高我也不敢用,我的做法是,把概率降到比你某天出門被花盆意外砸死的概率還要低,我就敢用了,因為我確信我不會某天出門被花盆砸死

  當然,明確了這一點,現在的問題就是,不會分析怎么辦?長遠來看,你還是回去好好學學概率,再回來分析得好;短期來看,有沒有簡單些的方法呢?當然有,就是測試。你隨機出很多很多數據,用你寫的快排去測,發現他們最慘的也完全可以算作O(NlogN),那基本就沒問題了,因為實際考試和實際應用數據情況也基本是這樣。

  好了,說了這么多,其實只是因為理解快排的思想是很多新手的一道門檻。我希望能通過自己多說些廢話,幫助很多新手順利邁過去。這樣,對很多新手以后的算法之路都是有益無害的。下面讓我們討論第一步實際應當如何做:

  必須明確,如果選擇方法過於復雜,那么算法常數會變大;如果方法過於簡單,那么算法復雜度會退化。因此綜合考慮,加以分析和大量實測,比較常見的既好寫又快的寫法有兩種。假設你有N個數A[1~N]:

1、x=mid(A[1],A[(1+N)/2],A[N]),mid是指取這三個數的中位數。這是最常用的一種方法,如果我沒記錯的話,這種算法也是C++算法庫(algorithm)里面的寫法。實際情況表明,這種取法效率很高。

2、x=A[randint(1,N)],也就是下標取1~N中隨機一個數。這也是比較常用的一種方法,好處是真正保證了隨機性,但壞處是生成隨機數耗時比較高,會導致算法常數變大。

 

       說了這么多,新手可能會覺得我還是沒說明為什么快排的復雜度是O(NlogN)。我只能說,要分析快排復雜度需要很細的分析和大量的數據,有機會我會單獨寫一篇文章來分析的,現在我只能從概率上告訴你大部分時候都是O(NlogN),而且快排常數比堆排小不少(時間大概快一倍吧,沒實測過,瞎說),能卡快排的數據你也暫時遇不到。我不敢說沒有數據能卡快排,但我可以確定,如果不是特意要卡你,這樣寫快排一定沒問題,反正我考試是敢用的。要是真有人死活卡你,那你就寫堆排吧,常數大點,但確實不可能被卡。

 

下面給出我的代碼:

 1 inline void swap(int &a,int &b) { int t=a; a=b; b=t; }
 2 
 3 inline int mid(int a,int b,int c)
 4 {
 5     if(a>b) swap(a,b);
 6     if(b>c) swap(b,c);
 7     if(a>b) swap(a,b);
 8     return b;
 9 }
10 
11 void QSort(int A[],int l,int r) // 升序
12 {
13     if(l>=r) return;
14     int i=l,j=r,x=mid(A[l],A[(l+r)>>1],A[r]);
15     while(true)
16     {
17         while(i<=r && A[i]<x) ++i;
18         while(l<=j && A[j]>x) --j;
19         if(i>j) break;
20         swap(A[i],A[j]); ++i; --j;
21     }
22     QSort(A,l,j); QSort(A,i,r);
23 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM