動畫: 快速排序 | 如何求第 K 大元素?

在這裏插入圖片描述

寫在前邊

我們有這麼一個需求,老闆和我們說,要求我們做這麼一個員工系統,公司員工的相關信息和爲公司的貢獻值都會在這個系統進行記錄,每到月底評功輪賞的時候,根據員工這一個月的表現進行獎罰。你可能會說,這還不好做嗎?增刪改查,然後直接按照貢獻值從大到小排序就好了。

彆着急,還有一個需求就是公司每個月都會進行抽獎福利,抽獎的方式是,老闆隨機抽取貢獻值爲第 K 大的貢獻值的員工送出福利一份,共選取 n 位,而不是評功論賞了,如果讓你實現一個系統,你該如何實現呢?

如果你學完今天的快速排序,就很輕鬆的解決老闆給你分配的任務啦。


思維導圖

在這裏插入圖片描述

1、什麼是快速排序?

顧名思義,快速排序,那肯定快呀,那到底有多快呢?快不過三秒?

假如我們已經接過老闆在數據庫給我們取出的本月每個員工的信息,我們單獨篩選出貢獻值,如下數據。

在這裏插入圖片描述
爲了能夠更加清晰的講解,我們對一些用到的特殊數據進行標識一下。

在這裏插入圖片描述

如上數據,我們從 p 到 q 隨機找一個元素作爲區分點(pivot),什麼是區分點?稍後我們解釋。我們就選擇最後一個數據 5 吧,然後我們以 5 爲區分點,然後從 p 開始遍歷元素,如果當前遍歷的元素小於 5,我們就放在 5 的前邊,如果當前遍歷的元素大於 5,我們就放在 5 的後邊,最後的結果如下:

看了上邊的一頓操作,我們也明白了爲什麼 5 是區分點了。上邊的數據也沒有從小到大呀?彆着急,重點來了。

我們是整體數據按照 5 爲區分點進行重新排列數據的,如果我們使用同樣的方式分別對 5 左邊和 5 右邊的數據分別進行這種方式的劃分,直到劃分到區間爲 1 爲止,是不是數據就會變的有序了?沒錯,這就是我們所說的快速排序。

有小夥伴會問到,這多麻煩,也快不過三秒呀?我們後邊會有性能分析的,到時候就知道快排比我們之前講的冒泡、插入有多快了。


2、動畫實現

在這裏插入圖片描述

3、快速排序的原理

雖然我們上邊籠統的分析了快速排序的基本過程,但是其中有兩個中要的知識點,快速排序的過程用到了遞歸和分治思想,我們分開進行分開講解。

3.1 遞歸

首先看一下快速排序的遞推公式,我們不斷的將大區間分割成小區間,然後對小區間再次進行分割。

我們可以總結出以上的遞推公式。

在這裏插入圖片描述

因爲我們不斷的將大區間分成小區間,然後一直分下去,不對,一直分總有一個盡頭的,所以這也是遞歸的終止條件。當滿足這個條件時,就不再繼續往下進行遞歸,那麼快速排序的遞歸條件是什麼呢?上邊也說到了,當區間只剩一個數據的時候,我們不再進行劃分,所以遞歸條件爲:

p >= q

遞歸的代碼實現:

在這裏插入圖片描述

3.2 分治思想

我們之所以將大問題不斷的分成小問題,就是用到的分治思想,分而治之,將分解的小問題解決了,大問題自然而然就會得到解決。

最關鍵的是快速排序中有一個分區函數 partition,分區函數的作用就是隨機找到一個區分點 pivot,然後對數據進行分區,該函數會返回分區後 pivot 的下標。

我們好奇的是如何進行分區的?我們需要用到一個分區函數 partition,我們想到最簡單的方法可能就是小於 pivot 的元素放到數組 a 中,大於 pivot 的元素放到數組 b 中,然後合併 a 和 b,完成分區。

如果我們不考慮空間上的消耗的話,這樣寫沒毛病的。但是,爲了考慮到空間上的消耗,也就是我們希望空間複雜度是 O(1),不得不讓分區函數佔用少的內存空間,我們需要在原數組中完成分區,而不是另外開闢新的空間。

這個過程我們單純的想是很難想出來的,而且非常有技巧性,所以我們一起來看一下。我們還是以上邊的數據爲例,從 p 開始遍歷元素,分別和 pivot 區分點元素進行比較,如果小於區分點元素,我們就進行交換,如果大於區分點元素,我們就不進行交換,我們具體來看一下動畫的實現。

在這裏插入圖片描述

4、快速排序的性能

我們知道快速排序的整個實現過程了,下面我們來分析一下快速排序的性能如何,不是你說很快嘛?能快過三秒嗎?

時間複雜度

我們先來看時間複雜度,快速排序時間複雜度的計算是分區操作的時間加上合併的時間,快速的時間複雜度爲 O(nlogn)。這是理想情況下,爲什麼呢?因爲我們隨機選擇區分點不可能每次都能將數據一分爲二。

還有一種極端的情況就是,如果原數據是一組有序數據,如果每次都要選擇最後一個元素爲區分點,大約需要進行 n 次操作,每次遍歷 n/2 個元素,所以時間複雜度就會推化成 O(n²)。

雖然存在這種情況,但是這種情況的概率是極低的,而且我們有方法可以將這種方法降到最低,在基礎環節,我們不多囉嗦。快速排序大部分情況下的平均時間複雜度爲 O(nlogn)。

空間複雜度

我們上邊也特別強調了,我們分區函數只需在原數組中進行分區操作就可以完成,不需要開闢額外的內存空間,所以空間複雜度爲 O(1)。

快速排序無論是時間效率還是空間效率,足以比我們之前講的冒泡排序和插入排序要效率高的多,在一些排序函數的框架源碼中,我們也會使用到快速排序,所以快排的應用還是非常廣泛的,所謂快不過三秒“真男人”。


5、代碼實現

JavaScipt 版本

在這裏插入圖片描述

Java 版本

在這裏插入圖片描述

小結

我們回到文章開頭的問題上,我們有一組員工的貢獻值數據,我們要隨機選取第 K 大的貢獻值的員工發放獎品,如何實現呢?

你可能會問,今天講的快速排序和這個問題有什麼直接的掛鉤呢?表面看起來並沒有什麼掛鉤,而這個問題的解決是對快速排序代碼的一個變體,稍微改動一下,就可以輕鬆解決上述問題。

比如幾位員工的貢獻值如下:7、9、4、3、6、2、5 。第 4 大元素就是 5,那就恭喜貢獻值爲 5 的員工獲得獎金一份,雖然實際情況下不太可能用這種方式發獎品,這裏我們只是拿這個例子來講。

我們將上邊的數據像快速排序一樣分爲三部分,分別爲 [0,p-1] p [p,q],這是已經完成分區函數的數據,因爲我們從 0 開始的,然後判斷當前的 p + 1 是否等於 K?如果等於 K ,那麼數組中下標爲 p 的元素就是第 K 大數據。

在這裏插入圖片描述

如上圖的 5 就是第四大數據,但是它在數組中的下標爲 3,所以需要加 1。


❤️ 不要忘記留下你學習的腳印 [點贊 + 收藏 + 評論]

文章+動畫寫了好幾個小時,不妨點贊支持一下。嘻嘻,你不點贊說明你很自私,你怕那麼好的文章讓別人也看到。開個小小玩笑。

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