快速排序 挖坑_由浅入深玩转快速排序算法
由淺入深玩轉快速排序算法
? ? ? 快速排序可以說是最快的通用排序算法,它甚至被譽為20世紀科學和工程領域的十大算法之一。在眾多排序算法中其無論是時間復雜度還是空間復雜度都頗具優勢。作為開發工程師,我們很有必要了解它的思想。接下來將由在下為大家一步步分析這一偉大排序算法的原理與實現思路。
主要從三個方向展開論述:
1、算法基本原理與實現:介紹兩種常用的實現思路。
2、性能特點:介紹算法高效的秘訣與其弱點。
3、算法優化:介紹PHP7與Java等成熟語言對算法的優化應用
1
快速排序算法基本原理
????????快速排序是一種分治的排序算法,即將大數組拆分成小數組處理。通過一趟排序將待排數組分成兩個子數組,使得一個子數組的元素均比另一個子數組的元素小。再分別將子數組進行上述排序,最終使得整個數組有序。其基本實現可以分為以下3步:
1、選中軸(pivot):從待排序的目標數組中挑選一個元素作為中軸元素。
2、分區(partition):把剩余的元素與中軸元素作比較,將小于等于中軸元素的放到中軸元素的左邊,將大于中軸元素的放到中軸元素的右邊。
3、 遞歸:以當前中軸元素的位置為界,將左子數組和右子數組看成兩個新的數組并重復上述操作,直到子數組的元素個數小于等于1。
????????快速排序算法可以有多種實現方式,選擇不同的中軸與選擇相同的中軸時均有不同的實現方法。下面舉出兩個例子供大家參考,方便大家理解實現思路。
1.1?左右掃描,交換元素
????????下圖quickSort1是采用了此方案實現的快速排序的一次分區過程,待排序數組是$arr = [4, 3, 8, 1, 6, 2, 7, 5];
1.2 左右掃描,挖坑補坑
????????在第一個實現思路中,當在左側找到大于中軸元素和在右側找到小于中軸元素的兩個元素位置時,需要對兩個元素進行交換。其中涉及到一個新的臨時變量的操作。下面的實現采用填坑的思路直接賦值,省去了臨時變量的讀寫操作。
????????下圖quickSort2是采用了一端挖坑一端補坑的快速排序的一次分區過程,待排序數組同樣是$arr = [4, 3, 8, 1, 6, 2, 7, 5];
2
性能特點
快速排序之所以快速主要有兩個原因:
1、內循環操作簡潔
????????在分區方法的內循環中僅采用遞增或遞減的索引將數組元素與一個定值作比較。并且沒有在內循環中移動數據,很難想象在排序算法中能有比這操作更簡潔的內循環了。
2、比較次數少
????????快速排序的效率依賴于切分數組的效果,即依賴于中軸元素的值。其最佳的情況是每次都正好將數組對半切分。在這種情況下快速排序的時間復雜度為O(Nlog2N)。而在每個子數組里面的數據也不會與其他子數組的數據做重復比較,大幅提升了效率。
????????由此我們也很容易看出快速排序的缺點:在分區不平衡的時候可能會出現極低的效率。快速排序最壞情況的時間復雜度為O(N2)。
3
算法優化
3.1 解決分區不平衡
????????由于中軸元素的選擇直接決定了快速排序的效率,為了使算法在數組逆序或將近有序等惡劣場景中都能達到高效率,我們可以采用以下辦法解決分區“一邊倒”的情況。
1、間接解決:在排序前使數組保持隨機性,即先對待排序數組進行亂序操作,再做快速排序,降低分區不平衡的概率。
2、直接解決:采用三數取中法或三取樣切分,隨機選取中軸元素。
3.2 切換到插入排序
????????對于小數組排序,插入排序比快速排序更快。因此在排序元素數量較小的數組時應該切換到插入排序。如PHP7的sort()排序函數的實現:在數組長度為 5~16 時采用插入排序否則采用快速排序。
(https://github.com/php/php-src/blob/PHP-7.4/Zend/zend_sort.c)
3.3 三切分快速排序
????????在實際應用中經常會出現含有大量重復元素的數組,例如我們需要將大量用戶數據按VIP等級排序或按生日日期排序。此時采用上述實現方案的經典快速排序則顯得有點笨,因為當一個子數組中的元素都是重復時,我們的算法仍然會將它切分成更小的數組遞歸排序。在有大量重復元素的情況下,這無疑會做很多無用功,使得時間復雜度提高到平方級別(O(N2))。而三向切分快速排序,則是為此而生,專門應對有大量重復元素數組的排序情況,是一種能把時間復雜度從線性對數級別(O(Nlog2N)) 降到線性級別(O(N))的算法實現。
????????基本實現思路:從左往右掃描數組,利用三個變量$i,$j,$k把數組分成4部分。
如下圖所示:
????????分區剛開始,選擇數組最左側的元素作為中軸元素。$i指向最左側元素,$k指向最左側元素的下一個元素,$j指向最右側的元素。
如下圖所示:
????????從左往右掃描數組,直到$k與$j相交($k > $j)。通過掃描,把未知元素按照其與中軸元素的大小關系放入不同的區間,不斷減少未知元素的數量,以完成分區。
????????當一次掃描結束后$i和$j分別指向了【=$pivot】區間的起始和結束位置。最后分別遞歸小于中軸部分的數組和大于中軸部分的數組,即可完成排序。
????????我們對有大量重復數據的數組進行排序,驗證三向切分快速排序的效果。(此處的測試僅是為了體現算法實現思想的差異會出現不同的性能效果,并不能嚴謹說明兩者的性能程度差距。)分別用1萬,10萬和50萬數據量的數組進行測試對比,數據生成規則是1-10內隨機生成,可以說數據重復概率極高。每個情況進行5次測試求平均值,得出以下數據表格:
????????可見在有大量重復數據的排序中,三向切分秒殺了經典快速排序。表中所示在對50萬級別的數據排序時,經典快速排序耗時高達48分鐘(等了好久才等到這個數據),而三向切分快速排序僅用了0.5秒。
????????而在重復元素較少的數組中,三向切分的性能并無優勢,相比經典快速排序需要消耗將近2倍的時間:
????????是否有一種實現方式既能兼顧有大量重復元素和低重復元素數組的排序呢?接下來,我們看看Java中Arrays.sort()的排序實現。
3.4 雙軸快速排序
在JDK1.7中給出了雙軸快速排序(DualPivotQuicksort)的思想,當然僅靠一個排序思想無法應對復雜的業務場景,為保證最高的排序效率,Java在實際排序中采用了一套相對成熟及復雜的方案,根據元素的數量及有序性采用了不同的排序方案。????????雙軸快速排序在有大量重復元素的排序中表現良好,同時能兼顧大量非重復元素的數組排序,其在實現思路上,跟三向切分快速排序有類似之處。已經理解了上面介紹過的三向切分,再來理解雙軸快速排序就不難了。大致實現思路如下圖所示:
????????完成分區后,分別對雙軸切分出來的三個區間進行遞歸排序即可。(受限于篇幅,以上所有算法實現只講述思路并未貼出代碼,如感興趣的同學可以找作者要PHP版的實現代碼。)
總結
????????本文主要講述了快速排序的原理及實現思路,總結為以下三點:
1、基本原理與實現:可采用元素交換或挖坑補坑的方式實現快速排序。
2、算法性能特點:
快速高效的兩個原因:其一內循環操作簡潔;其二比較次數較少
性能弱點:中軸元素選擇不當可能導致性能極其低下
3、算法優化:
解決分區不平衡的2種辦法
對小數組排序采用插入排序
對有大量重復元素的數組采用三向切分快速排序
JDK1.7中雙軸快速排序的實現思路
參考文獻:
[1]《算法(第4版)》
[2]《QUICKSORTING - 3-WAY AND DUAL PIVOT》https://rerun.me/2013/06/13/quicksorting-3-way-and-dual-pivot/
[3]《Java中雙基準快速排序方法的具體實現》http://www.mamicode.com/info-detail-2395124.html
排版 |川芮
總結
以上是生活随笔為你收集整理的快速排序 挖坑_由浅入深玩转快速排序算法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 剪映app如何降低版本
- 下一篇: 多节锂电串联保护板ic_如何有效保护锂电