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