知識點7:常見的排序算法--快速排序

快速排序原理

快速排序源於分治策略,是指將一個整體劃分爲規模更小,但是結構和原理都相同的小部分進行遞歸操作,最終得出原問題的結果。
恩,看不懂,難理解,怎麼辦?不怕,我們來舉幾個假設:
假設1:對數組【9,4】按從小到大進行排序,怎麼辦呢?那當然是 9>4 ,所以交換兩者,結果爲【4,9】。多簡單,是吧。
假設2:對數組【4,2,9】按從小到大進行排序呢?如果以分治算法爲例的話,它是這樣的:

1.對確定的數組,選定一個基底,將其作爲數組排序的分界線,小的往左,右的往大。在這裏,以數組首元素【4】爲例進行劃分,得出【2,4,9】。還可以是吧。
那麼,可能會有人問:怎麼去進行劃分呢?我們來看一下:
首先,選定了基底【4】,將其保存在變量base中。,取左下標(left)和有下標(right),代表需要排序的數組空間。初始化時一般爲left=0;right = 數組的長度-1。
然後,我們從數據右側開始進行比較,如果數組右側的數字>基底,則往前移一位(right-1),再進行比較,如此循環,直到右側的數字<=基底或者left>right時
此時,取其下標,例子中爲【1】,如果此時right>left ,則把數組right下標的值賦值給left對應的下標。
接着,從數組的左側開始比較,如果數組左側的數字 <= 基底,則往後挪一位(left+1),再進行比較,如此循環,直到左側的數字>基底或者left>ight時此時,取其下標,例子中爲【2】  如果此時left<right ,則把數組left下標的值賦值給right對應的下標。
把base的值賦值給arr[left].
是不是有點似懂非懂的感覺了?不怕,我們再來詳細擴展一下:
假設3:對數組【4,2,9,8,3,6,5】按從小到大進行排序呢?
首先,選定基底(4)(一般爲數組首元素,因爲易於控制)
其次,從右側開始比較,此時下標(right)=6。
    ->   (5>4)   ->right-1(5),對應值爲6  ->   (6>4)  -> right-1(4),對應值爲3  -> (3<4) ,停止循環 ,right = 4
    數據交換->  (4>0)    ->a[0]=a[4]  ->[3 2 9 8 3 6 5]
 接着。從左側開始比較,此時下標(left) = 0;
    ->   (4==4)  ->left+1(1),對應值爲2   ->   (2<4)   ->left+1(2),對應值爲9  ->  (9>4)  ,停止循環 ,left = 2;
    數據交換->(2 < 4)  ->a[4]=a[2] ->[3 2 9 8 9 6 5]  
最後,將base保存的基底賦值爲left ,即a[2] = base ->[3 2 4 9 8 6 5]
看到這裏,細心的你是否發現問題了呢?我們一開始確定的基數爲【4】,如今經過運算後,發現我們已經以4爲分界線。小的往左,有的往大了是吧。是就對應了我們在假設2中說的分治算法的思路。也就是說,我們已經把數組分成了兩個小部分。但是這兩部分裏面數組還是無序的,所以我們就要開始遞歸操作這個步驟,直到把數組分解兩個數字爲一組的小部分,按照假設1進行排序,最終得出的結果就是我們想要的啦。我們再耗些腦細胞,繼續推斷下去吧~
前面說到,經過分治算法的策略,我們把【4,2,9,8,3,6,5】變成了【3,2,4,9,8,6,5】
那麼,接下來,我們把數組分成3部分【【3,2】【4】【9,8,6,5】】
因爲中間本來是我們的基底,所以不再計算,把目光瞄向左側和右側分別再進行分治策略。
左側爲例:
可以假設爲:對數組【3,2】按從小到大進行排序:
1.確定基底 3 (base)
2.從右側開始比較 ,直到 arr[right] <= base ,此時 right = 1 因爲 right>left -> a[left]=arr[right] ->【2,2】
3.從左側開始比較,直到arr[left] >base 但運算後發現right == left(都爲1),退出循環
4.將base保存的基底複製爲left ,即a[1] = base ->[2 3]
右側爲例:
可以假設爲:對數組【9,8,6,5】按從小到大進行排序:
1.確定基底9(base)
2.從右側開始比較 ,直到 arr[right] <= base ,此時right=3; 因爲right>left -> a[left] = arr[right]  ->【5,8,6,5】
3.從左側開始比較,直到arr[left] >base 但運算後發現right == left(都爲3),退出循環
4.將base保存的基底複製爲left ,即a[3] = base ->[5,8,6,9]
此時,再次完成分治,可以把數組分成【【5,8,6】,【9】】
繼續執行假設:
可以假設爲:對數組【5,8,6】按從小到大進行排序:
1.確定基底5(base)
2.從右側開始比較 ,直到 arr[right] <= base 但運算後得出left == right (都爲0),退出循環
3.從左側開始比較,直到arr[left] > base ,此時left=0;因爲 left==right 不執行賦值操作
4.將base保存的基底複製爲left ,即a[0] = base ->[5,8,6]
此時,繼續完成分治(有人說,有嗎,它明明就是不變啊,怎麼會分治了?如果我們再以基底爲分界線的話,你可能就發現它可以分成【【5】,【8,6】】,這自然就是分治了是吧)
繼續假設:對數組【8,6】按從小到大進行排序:
1.確定基底 8 (base)
2.從右側開始比較 ,直到 arr[right] <= base ,此時 right = 1 因爲 right>left -> a[left]=arr[right] ->【6,6】
3.從左側開始比較,直到arr[left] >base 但運算後發現right == left(都爲1),退出循環
4.將base保存的基底複製爲left ,即a[1] = base ->[6,8]

好了,到了這一步,我們可算是把整一個分治算法給腦補了一遍,可不可怕,真可怕啊。但是我們也有了收穫,不是嗎,仔細觀察我們的思路,你就發現,我們其實就是不斷地把大問題分解成小問題(分治策略),然後再小問題裏裏面重復着相應的操作,直到得出這個結果(遞歸)。所以,到了這裏,你應該瞭解分治策略和遞歸了吧~
好了,既然知道了套路,那麼我們就把套路變成代碼吧~

快速排序的實現

void quickSort(int[] arr,int low,int height){
/*控制程序,在以上分析中,我們出現基底恰好就是最大或者最小值的情況
*此時,數組不會有兩種情況,故而需要對程序進行控制,對不符合條件的數組進行終止
*/
if(low>height){
    return;
    }

//第一步:
int base = arr[low];    
int left = low;
int right = height;
while(left != right){
    //第二步:
    while( left<right  && arr[right] > base){
        right--;
    }
    if(right>left){
        arr[left] = arr[right];
    }
    //第三步:
    while(left<right && arr[left] <= base){
        left++;
    }
    if(left<right){
        arr[right] = a[left];
    }
}
//第四步
arr[left] = base;

quickSort(arr,low,left-1);
quickSort(left+1,height);   
}

至此,我們的快速排序算法算告一段落了,代碼中註釋的部分建議對着上面的原理裏面的思路來分析爲什麼要這麼寫,有沒有更好的寫法之類的,多想這些問題,可以幫助你更好地理解快速排序的思路,所以該勤奮的時候就不要懶惰,趕緊趁現在來梳理一下思路吧~

下一章:知識點8:常見的排序算法–選擇排序,敬請期待

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章