14-排序優(yōu)化:如何實(shí)現(xiàn)一個(gè)通用的常摧、高性能的排序函數(shù)?

如何實(shí)現(xiàn)一個(gè)通用的凿试、高性能的排序函數(shù)排宰?

如何選擇合適的排序算法似芝?

先回顧一下前面寫過的幾種排序算法:

  • 線性排序算法的時(shí)間復(fù)雜度比較低,適用場景比較特殊板甘。所以寫一個(gè)通用的排序函數(shù)党瓮,不能選擇線性排序算法。

  • 對小規(guī)模數(shù)據(jù)進(jìn)行排序盐类,可以選擇時(shí)間復(fù)雜度是 O(n^2) 的算法寞奸;如果對大規(guī)模數(shù)據(jù)進(jìn)行排序,時(shí)間復(fù)雜度是 O(nlogn)的算法更加高效在跳。所以枪萄,為了兼顧任意規(guī)模數(shù)據(jù)的排序,一般都會(huì)首選時(shí)間復(fù)雜度是 O(nlogn) 的排序算法來實(shí)現(xiàn)排序函數(shù)猫妙。

時(shí)間復(fù)雜度是 O(nlogn) 的排序算法不止一個(gè)瓷翻,有歸并排序、快速排序割坠、還有后面會(huì)講到的堆排序齐帚。堆排序和快速排序都有比較多的應(yīng)用,比如 Java 語言采用堆排序?qū)崿F(xiàn)排序函數(shù)彼哼,C 語言使用快速排序?qū)崿F(xiàn)排序函數(shù)对妄。

使用歸并排序的情況其實(shí)并不多。我們知道敢朱,快排在最壞情況下的時(shí)間復(fù)雜度是 O(n^2)剪菱,而歸并排序可以做到平均情況、最壞情況下的時(shí)間復(fù)雜度都是 O(nlogn)拴签,從這點(diǎn)上看起來很誘人孝常,為什么它還是沒能得到“寵信”呢?

歸并排序并不是原地排序算法篓吁,空間復(fù)雜度是 O(n)茫因。所以,粗略點(diǎn)講排序算法空間耗費(fèi)會(huì)翻倍杖剪。

快速排序比較適合來實(shí)現(xiàn)排序函數(shù),但是驰贷,我們也知道盛嘿,快速排序在最壞情況下的時(shí)間復(fù)雜度是 O(n^2),如何來解決這個(gè)“復(fù)雜度惡化”的問題呢括袒?

如何優(yōu)化快速排序次兆?

為什么最壞情況下快速排序的時(shí)間復(fù)雜度是 O(n^2) 呢?我們講過锹锰,如果數(shù)據(jù)原來就是有序的或者接近有序的芥炭,每次分區(qū)點(diǎn)都選擇最后一個(gè)數(shù)據(jù)漓库,那快速排序算法就會(huì)變得非常糟糕,時(shí)間復(fù)雜度就會(huì)退化為 O(n^2)园蝠。

實(shí)際上渺蒿,這種 O(n^2) 時(shí)間復(fù)雜度出現(xiàn)的主要原因還是因?yàn)槲覀兎謪^(qū)點(diǎn)選的不夠合理。

那什么樣的分區(qū)點(diǎn)是好的分區(qū)點(diǎn)呢彪薛?或者說如何來選擇分區(qū)點(diǎn)呢茂装?最理想的分區(qū)點(diǎn)是:被分區(qū)點(diǎn)分開的兩個(gè)分區(qū)中,數(shù)據(jù)的數(shù)量差不多善延。

這里介紹兩個(gè)比較常用少态、比較簡單的分區(qū)算法,你可以直觀地感受一下易遣。

  1. 三數(shù)取中法

我們從區(qū)間的首彼妻、尾、中間豆茫,分別取出一個(gè)數(shù)侨歉,然后對比大小,取這 3 個(gè)數(shù)的中間值作為分區(qū)點(diǎn)澜薄。這樣每間隔某個(gè)固定的長度为肮,取數(shù)據(jù)出來比較,將中間值作為分區(qū)點(diǎn)的分區(qū)算法肤京,肯定要比單純?nèi)∧骋粋€(gè)數(shù)據(jù)更好颊艳。但是,如果要排序的數(shù)組比較大忘分,那“三數(shù)取中”可能就不夠了棋枕,可能要“五數(shù)取中”或者“十?dāng)?shù)取中”。

  1. 隨機(jī)法

隨機(jī)法就是每次從要排序的區(qū)間中妒峦,隨機(jī)選擇一個(gè)元素作為分區(qū)點(diǎn)重斑。這種方法并不能保證每次分區(qū)點(diǎn)都選的比較好,但是從概率的角度來看肯骇,也不大可能會(huì)出現(xiàn)每次分區(qū)點(diǎn)都選的很差的情況窥浪,所以平均情況下,這樣選的分區(qū)點(diǎn)是比較好的笛丙。時(shí)間復(fù)雜度退化為最糟糕的 O(n^2) 的情況漾脂,出現(xiàn)的可能性不大。

如果想了解更多尋找分區(qū)點(diǎn)的方法胚鸯,可以自己課下深入去學(xué)習(xí)一下骨稿。

快排是遞歸來實(shí)現(xiàn)的,為了避免快速排序里遞歸過深而堆棧過小,導(dǎo)致堆棧溢出坦冠,有兩種解決辦法:

第一種是限制遞歸深度形耗。一旦遞歸過深,超過了我們事先設(shè)定的閾值辙浑,就停止遞歸激涤。

第二種是通過在堆上模擬實(shí)現(xiàn)一個(gè)函數(shù)調(diào)用棧,手動(dòng)模擬遞歸壓棧例衍、出棧的過程昔期,這樣就沒有了系統(tǒng)棧大小的限制填抬。

舉例分析排序函數(shù)

為了讓大家對如何實(shí)現(xiàn)一個(gè)排序函數(shù)有一個(gè)直觀的感受晚顷,拿 Glibc 中的 qsort() 函數(shù)舉例說明一下。雖說 qsort() 從名字上看鳍贾,很像是基于快速排序算法實(shí)現(xiàn)的梦抢,實(shí)際上它并不僅僅用了快排這一種算法般贼。

看源碼,你就會(huì)發(fā)現(xiàn)奥吩,qsort() 會(huì)優(yōu)先使用歸并排序來排序輸入數(shù)據(jù)哼蛆,因?yàn)闅w并排序的空間復(fù)雜度是 O(n),所以對于小數(shù)據(jù)量的排序霞赫,比如 1KB腮介、2KB 等,歸并排序額外需要 1KB端衰、2KB 等叠洗,歸并排序額外需要 1KB、2KB 的內(nèi)存空間旅东,這個(gè)問題不大∶鹨郑現(xiàn)在計(jì)算機(jī)的內(nèi)存都挺大的,我們很多時(shí)候追求的是速度抵代。還記得我們前面講過的用空間換時(shí)間的技巧嗎腾节?這就是一個(gè)典型的應(yīng)用。

但如果數(shù)據(jù)量太大荤牍,就跟我們前面提到的案腺,排序 100MB 的數(shù)據(jù),這個(gè)時(shí)候我們再用歸并排序就不合適了康吵。所以救湖,要排序的數(shù)據(jù)量比較大的時(shí)候,qsort() 會(huì)改為用快速排序算法來排序涎才。

那 qsort() 是如何選擇快速排序算法的分區(qū)點(diǎn)的呢?如果去看源碼,你就會(huì)發(fā)現(xiàn)耍铜,qsort() 選擇分區(qū)點(diǎn)的方法就是“三數(shù)取中法”邑闺。

還有我們前面提到的遞歸太深會(huì)導(dǎo)致堆棧溢出的問題,qsort() 是通過自己實(shí)現(xiàn)一個(gè)堆上的棧棕兼,手動(dòng)模擬遞歸來解決的陡舅。

實(shí)際上,qsort() 并不僅僅用到了歸并排序和快速排序伴挚,它還用到了插入排序靶衍。在快速排序的過程中,當(dāng)要排序的區(qū)間中茎芋,元素的個(gè)數(shù)小于等于 4 時(shí)颅眶,qsort() 就退化為插入排序,不再繼續(xù)用遞歸來做快速排序田弥,因?yàn)槲覀兦懊嬉仓v過涛酗,在小規(guī)模數(shù)據(jù)面前,O(n^2) 時(shí)間復(fù)雜度的算法并不一定比 O(nlogn) 的算法執(zhí)行時(shí)間長偷厦。我們現(xiàn)在就來分析下這個(gè)說法商叹。

算法的性能可以通過時(shí)間復(fù)雜度來分析,但是只泼,這種復(fù)雜度分析是比較偏理論的剖笙,如果我們深究的話,實(shí)際上時(shí)間復(fù)雜度并不等于代碼實(shí)際運(yùn)行時(shí)間请唱。

時(shí)間復(fù)雜度代表的是一個(gè)增長趨勢弥咪,如果畫成增長曲線圖,你會(huì)發(fā)現(xiàn) O(n^2) 比 O(nlogn) 要陡峭籍滴,也就是說增長趨勢要更猛一些酪夷。但是,我們前面講過孽惰,在大 O 復(fù)雜度表示法中晚岭,我們會(huì)省略低階、系數(shù)和常數(shù)勋功,也就是說坦报,O(nlogn) 在沒有省略低階、系數(shù)狂鞋、常數(shù)之前可能是 O(knlogn + c)片择,而且 k 和 c 有可能還是一個(gè)比較大的數(shù)。

假設(shè) k=1000骚揍,c=200字管,當(dāng)我們對小規(guī)模數(shù)據(jù)(比如 n=100)排序時(shí)啰挪,n^2 的值實(shí)際上比 knlogn+c 還要小。

knlogn+c = 1000 * 100 * log1000 + 200 遠(yuǎn)大于 10000
n^2 = 100*100 = 10000

所以嘲叔,對于小規(guī)模數(shù)據(jù)的排序亡呵,O(n^2) 的排序算法并不一定比 O(nlogn) 排序算法執(zhí)行的時(shí)間長。對于小數(shù)據(jù)量的排序硫戈,我們選擇比較簡單锰什、不需要遞歸的插入排序算法。

還記得我們之前講到的哨兵來簡化代碼丁逝,提高執(zhí)行效率嗎汁胆?在 qsort() 插入排序的算法實(shí)現(xiàn)中,也利用了這種編程技巧霜幼。雖然哨兵可能只是少做一次判斷嫩码,但是畢竟排序函數(shù)是非常常用、非承谅樱基礎(chǔ)的函數(shù)谢谦,性能的優(yōu)化要做到極致。

好了萝衩,C 語言的 qsort() 我已經(jīng)分析完了回挽,你有沒有覺得其實(shí)也不是很難?基本上都是用了我們前面講到的知識(shí)點(diǎn)猩谊,有了前面的知識(shí)積累千劈,看一些底層的類庫的時(shí)候是不是也更容易了呢?

小結(jié)

今天分析了一下如何來實(shí)現(xiàn)一個(gè)工業(yè)級(jí)的通用的牌捷、高效的排序函數(shù)墙牌,內(nèi)容比較偏實(shí)戰(zhàn),而且貫穿了一些前面幾節(jié)的內(nèi)容暗甥,你要多看幾遍喜滨。我們大部分排序函數(shù)都是采用 O(nlogn) 排序算法來實(shí)現(xiàn),但是為了盡可能地提高性能撤防,會(huì)做很多優(yōu)化虽风。

著重講了快速排序的一些優(yōu)化策略,比如合理選擇分區(qū)點(diǎn)寄月、避免遞歸太深等等辜膝。最后,還分析了一個(gè) C 語言中 qsort() 的底層實(shí)現(xiàn)原理漾肮,希望你對此能有一個(gè)更加直觀的感受厂抖。

課后思考

在今天的內(nèi)容中,我分析了 C 語言的中的 qsort() 的底層排序算法克懊,你能否分析一下你所熟悉的語言中的排序函數(shù)都是用什么排序算法實(shí)現(xiàn)的呢忱辅?都有哪些優(yōu)化技巧七蜘?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市耕蝉,隨后出現(xiàn)的幾起案子崔梗,更是在濱河造成了極大的恐慌,老刑警劉巖垒在,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異扔亥,居然都是意外死亡场躯,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門旅挤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來踢关,“玉大人,你說我怎么就攤上這事粘茄∏┪瑁” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵柒瓣,是天一觀的道長儒搭。 經(jīng)常有香客問我,道長芙贫,這世上最難降的妖魔是什么搂鲫? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮磺平,結(jié)果婚禮上魂仍,老公的妹妹穿的比我還像新娘。我一直安慰自己拣挪,他們只是感情好擦酌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著菠劝,像睡著了一般赊舶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上闸英,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天锯岖,我揣著相機(jī)與錄音,去河邊找鬼甫何。 笑死出吹,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的辙喂。 我是一名探鬼主播捶牢,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鸠珠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了秋麸?” 一聲冷哼從身側(cè)響起渐排,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎灸蟆,沒想到半個(gè)月后驯耻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡炒考,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年可缚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片斋枢。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡帘靡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瓤帚,到底是詐尸還是另有隱情描姚,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布戈次,位于F島的核電站轩勘,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏朝扼。R本人自食惡果不足惜赃阀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望擎颖。 院中可真熱鬧榛斯,春花似錦、人聲如沸搂捧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽允跑。三九已至王凑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間聋丝,已是汗流浹背索烹。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留弱睦,地道東北人百姓。 一個(gè)月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像况木,于是被迫代替她去往敵國和親垒拢。 傳聞我的和親對象是個(gè)殘疾皇子旬迹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容