由於最近在幫一個學長整理考研數據結構的真題部分,很有感悟,所以今天終於下定決心寫博客了,由於是第一次寫如果有誤請多多包涵quq,並且能指出來的話感激不盡。
大一第一次看快速排序的時候也是看的一臉懵逼,只是大概懂了怎麼回事,現在把它好好整理出來(參考資料結合自己),希望能對像當時的我一樣頭大的人一點幫助(雖然網上已經有其他各種版本的講解。。),並且我會盡可能以通俗易懂的語言來講述。同時對於考研的考點我也會做標註,以供自己和複習的同學使用,如果不需要的請無視它們就好了(*/ω\*)
如果想要學習本排序方法請閱讀全文,如果只是想要代碼請直接拉到最後方位置。
1.基本原理
快速排序最早是由圖靈獎得主Tony.Hoare於1962年提出的一種劃分交換排序,它是從冒泡排序改進而得到的一種“交換”排序方法,通過一趟排序將記錄分割成獨立的兩部分。它採用了一種分治策略。
它的基本思想(以從小到大排序爲例)是: ←考點
(1)從數列中取出一個數作爲基準數(書上也稱它爲樞軸);
(2)把數列中比基準數小的數都放在它的左邊,比基準數大的數都放在它的右邊;
(3)再對被放好的左右兩堆數重複步驟(2)進行劃分,直到各區間只有一個數,此時整個數列有序。
我們把將待排序的一堆數劃分爲兩堆數的過程成爲一次劃分或一趟快速排序。 ←考點
2.基本操作
一趟快速排序的基本操作是:(爲了方便閱讀和理解我劃分了步驟)
1.假如待排數列爲(,,…,,),在其中任選一個數Ri作爲基準數(取題目中要求排序的位置的第一個,比如排第2-5個數,則取i=2),將它的值保存爲臨時變量val(以防後面比較的時候丟失)。
2.然後凡是比基準值小的都移動到它的前面,凡是比它大的都移動到它的後面,那麼在一趟排序後,數列就被分成了兩部分:R[s…i-1]和R[i+1…t]。既然要分成兩部分那我們自然要有能比較的標準,因此設兩個指針low和high來指定要比較的數,採取左右開弓的策略:low指向數列的第一個位置,high指向數列的最後一個位置。
3.首先將high指向的值(假設爲R[high].key)與基準數val進行比較,如果R[high]≥val,那麼high減一(即向左移動一位),否則將high指向的數移動到low指針指向的位置(作用是把比val小的移動到它的左邊);一直到了high指針指向的數移動在low指針後,再看low指針,如果R[low].key≤val,則low加一(即向右移動一位)。
4.重複上述兩個方向的大小比較,直到這兩個指針指向的位置重合。
3.過程演示
如果你對上面的文字描述還有疑問,那麼可以看看下面的畫圖演示XDD,以下爲排序全部數列。則基準值爲r[0]。
一趟快速排序:
接下來再對分好的左右兩堆數進行排序,整個過程可通過遞歸過程進行。若待排的數列中只有一個數字了,則說明它已經有序,不再需要排序;否則對該數列進行一次遞歸快速排序。
快速排序總過程:
4.代碼實現
一趟快速排序算法如下:
1int FindPos(int *a,int low,int high)
2{
3 int val=a[low];//把low指針指向的數存入val中
4 while(low<high)
5 {
6 //兩個指針還未重疊且high指向的數字比基準值大
7 while(low<high&&a[high]>=val)
8 {--high;}//high指向減一
9 a[low]=a[high];//否則high指向的值移動到low指向的位置處
10 //當high指向的數移動到low處後再進行low指針的判斷
11 while(low<high&&a[low]<=val)
12 {++low;}
13 a[high]=a[low];
14 }//終止while循環後low和high值相等
15 a[low]=val;//相當於把val的值填入空缺地方
16 return low;//high也行
17}
快速排序的函數如下:
1void QuickSort(int *R,int low,int high)
2{
3 int pos;//pos爲基準值的位置
4 if(low<high)//數組長度大於一
5 {
6 pos=FindPos(R,low,high);//對待排數列進行一次劃分,並返回基準值的位置
7 QuickSort(R,low,pos-1);//對基準值左邊的數列進行遞歸排序
8 QuickSort(R,pos+1,high);//對基準值右邊的數列進行遞歸排序
9 }
10}
完整代碼實現快速排序:
1#include<stdio.h>
2void QuickSort(int *,int,int);
3int FindPos(int *,int,int);
4int main(void)
5{
6 //設定爲爲已知長度數列排序
7 int r[8]={49,38,65,48,74,13,27,52};
8 int i;
9 QuickSort(r,0,7);//第二個參數表示要排序位置的第一個元素的下標,第三個參數表示最後一個元素的下標,可以按照題目要求修改
10 for(i=0;i<8;i++)
11 printf("%d ",r[i]);
12 return 0;
13}
14
15void QuickSort(int *r,int low,int high)
16{
17 int pos;
18 if(low<high)//數組長度大於一
19 {
20 pos=FindPos(a,low,high);
21 QuickSort(a,low,pos-1);
22 QuickSort(a,pos+1,high);
23 }
24}
25
26int FindPos(int *r,int low,int high)
27{
28 int val=r[low];
29 while(low<high)
30 {
31 while(low<high&&r[high]>=val)
32 --high;
33 r[low]=r[high];
34
35 while(low<high&&r[low]<=val)
36 ++low;
37 r[high]=r[low];
38 }//終止while循環後low和high值相等
39 r[low]=val;
40 return low;//high也行
41}
運行結果:
5.時間、空間複雜度分析
快速排序的一次劃分從兩頭交替搜索,直到low和high重合,時間複雜度是O(n),整個算法的時間複雜度與劃分的趟數有關,也就是說,快速排序的時間性能取決於快速排序遞歸的深度。如下圖所示,可以用遞歸樹來描述遞歸過程的執行情況(一趟快速排序)。其中根就是以“49”爲基準值的劃分,左子樹爲所有值比它小的,右子樹爲所有比它大的。此劃分過程比較均勻(一邊三一邊四),遞歸樹是平衡的,此時性能比較好。
在最優情況下,每次劃分都比較均勻,如果排序n個數字,其遞歸樹的深度爲lbn(lb是以2爲底的對數),即僅需要遞歸lbn次。這樣整個算法的時間複雜度爲O(nlbn)。 ←考點
最壞情況是,每次所選的基準值都是當前序列中最大或者最小的元素,這使得每次劃分所得的兩堆數中有一堆是空的,另一堆爲原來的長度減一。這樣就需要n趟劃分,整個算法的時間複雜度爲O()。 ←考點
快速排序的平均時間複雜度也是O(nlbn),因此,該排序方法被認爲是目前最好的一種內部排序。
從空間複雜度來看,儘管快排需要一個元素(val)的輔助空間,由於它是遞歸的,每層遞歸調用時的指針和參數均要用棧來存放,存儲開銷在理想的情況下爲O(lbn);在最壞的情況下爲O(n)。
另外,在快速排序過程中,如果碰到有相同的元素,那麼他們的相對位置是會發生改變的(可以自己試試看),因此,快速排序是不穩定的排序方法。 ←考點
最後,基準值的選取是影響算法性能的關鍵,輸入數據的次序隨機性越好,排序的性能也就越好。
第一次寫博客還真的有好多不足。。前前後後弄了快6個小時。。沒有寫過是一方面,但更重要的沒有很好的掌握它,導致寫的時候思路斷斷續續的,浪費了很多的時間。以後一定要多花時間寫博客,整理知識點什麼的。(*・ω< )