快排其實很簡單
大學時期,老師只講了課本上快排的一種實現方式,當時學的雲裡霧裡,最近調研一下知乎上的一些實現方式,總結歸納了一下,發現還是非常簡單的,只要清晰了具體概念,實現方式就可以隨意發揮了(至於可讀性,簡易性當然各有千秋)。
什麼是快排
- 從數列中挑出一個元素,稱為“基準”(pivot),
- 重新排序數列,所有比基準值小的元素擺放在基準前面,所有比基準值大的元素擺在基準後面(相同的數可以到任何一邊)。在這個分割槽結束之後,該基準就處於數列的中間位置。這個稱為分割槽(partition)操作。
- 遞迴地(recursively)把小於基準值元素的子數列和大於基準值元素的子數列排序。
換種說法
- 隨意找個對比基準
- 將所有小於 基準 的元素放到它前面,大於它的放到後面,這樣該基準的位置即為它的最終位置(不然呢?)
- 遞迴地切分基準左右兩側的子列表,重複 1
有木有感覺清晰了很多~
再看一下執行中遞迴的結構樹:
從圖中可以看出,排序是自上而下的,快排的排序操作應當是在遞進的過程中, 整個過程中,除了葉子每一個元素都是基準元素。
Code Time
錯誤的示例
這裡只要寫好最核心的一步(對樹單層中基準位置的調整)函式,再配合基本的遞迴結構,就 OK 了。在知乎上找了一個比較清晰的一種實現方式:
/**
* 作為遞迴的入口
* @left 當前函式幀中輸入陣列的起始點
* @right 當前函式幀中輸入陣列的終點
*/
function quickSort(arr, left, right) {
const i = correctPivot(arr, left, right)
if(curPivot > i) quickSort(arr, left, i)
if(curPivot < i) quickSort(arr, i + 1, right)
}
function correctPivot(arr, i, j) {
let mid = arr[Math.floor((i + j)/2)]
while(i < j) {
while(arr[i] < mid) i++
while(arr[j] > mid) j--
if(i<=j) {
swap(arr, i, j) // 交換 i 和 j 位置上的元素,很簡單,用輔助變數 tmp
i++, j--
}
}
return i
}
將原始碼拆分為了如上函式,經過模擬分析,發現這個核心函式就是錯的,它以 i 和 j 最終匯合的地點為新的切分點,而不是當前基準點的位置,因此基準很可能依舊在待排序的子陣列中,這樣在接下來的層級中,依舊還需要對老的基準進行對比(移動不會,因為已經是到最終的位置了),這樣無形間增加了時間複雜度,完全違背了利用分治來分解問題的初衷,而且還可能因為條件判斷異常導致棧溢位。
《資料結構》課本上的解法
因為我是計科科班出身,因此有必要曬一下被教育部認可的排序演算法:
function quickSort(arr, first, end) {
if(first >= end) return // 遞迴到葉子節點
const pivot = correctPivot(arr, first, end)
quickSort(arr, first, pivot - 1)
quickSort(arr, pivot + 1, end)
}
function correctPivot(arr, first, end) {
while(first < end) {
while(first < end && arr[first] <= arr[end]) end-- // 右側掃描
if(first < end) {
swap(arr, fist, end)
first++
}
while(first < end && arr[first] <= arr[end]) first++ // 左側掃描
if(first < end) {
swap(arr, fist, end)
end--
}
}
return first
}
在已知 corretPivot
函式返回的是基準最終的位置,因此我們只需要不斷操作最終得到它的 index 即可。
解析 correctPivot
- 由於在移動 first 和 end 的時候,二者重合之際即是基準的最終位置
- 這裡我們是預設以第一個元素為基準,掃描基準右側,讓 end 向左一直移動到比基準小的元素的位置,然後交換基準與該元素的位置,此時該元素已經確定在基準左側,所以不再處於對比範圍(因為我們只是在找基準的位置,無需關心基準以外元素的狀態,只需記住小於基準的一定都要在左側,大於之的在另一側),故 first 後移
- 開始掃描(基準的)左側,一直移動到比基準大的位置(表示該數應處於基準的右側),交換之,end 前移。
- 重複 2、3 步驟
其實在每次掃描時,都能夠讓基準處於 first 或者 end 的位置上,比如掃描完右側,那麼基準必然是會在 end 的位置,反之依然。這裡對一側可以簡單的理解為基準的另一側。在交換之前,每次在移動遊標(first 或者 end) 的時候,都是在順序地排除比基準都大(小)的元素,縮小對比範圍,每次的比較範圍都是 first~end,直到 first === end
時,表示它兩側都是比它小或大的元素。
《演算法導論》
function correctPivot(arr, l, r) {
let m = l - 1
for (let i = l; i <= r; i++) {
if (arr[i] < arr[r]) {
swap(++m, i)
}
}
swap(m+1, r)
return m + 1
}
有木有太簡單了,只用了一次迴圈就搞定了基準的位置。
解析
先確定一件事,就是 m + 1 才是基準的最終位置,而我們每一次執行函式都是以最後一個元素為基準的。
- 前移 m
- 從前向後遍歷整個陣列(這裡指的是 l ~ r 範圍內的部分), 如果發現該元素小於基準,則後移 m,並將當前元素和 m 位置上的元素交換。(這裡相當於是把小於基準的所有元素都插入到 m 的位置)
- 迴圈結束,m 的位置為最後一個比基準小的元素。
- 將基準移動到 m + 1 的位置,結束。
相關文章
- Promise 其實很簡單Promise
- 其實泛型很簡單泛型
- 紅黑樹其實很簡單
- Redux其實很簡單(原理篇)Redux
- 高大上的詞雲,其實很簡單
- 執行緒池其實看懂了也很簡單執行緒
- 撥開雲霧見月明—計算其實很簡單
- 這樣看java記憶體模型其實很簡單Java記憶體模型
- 這是DDD建模最難的部分(其實很簡單)
- android 整合微信支付和支付寶支付其實很簡單Android
- 有了它,在家遠端辦公其實可以很簡單
- 新手也能看懂,訊息佇列其實很簡單佇列
- 如何做到數字化轉型?其實方法很簡單
- 月薪3000和30000的人差在哪?知道答案後其實很簡單
- Python 基礎起步 (九) 條件語句 if elif else 其實很簡單Python
- EventLoop其實如此簡單OOP
- js冒泡、快排的簡單寫法JS
- 短影片一鍵同步系統,同步30+平臺其實很簡單
- 優思學院|精益六西格瑪很深奧?其實很簡單
- 簡單的單例模式其實也不簡單單例模式
- H3C UniStor CB備份一體機:備份其實很簡單
- 老股民總結的8個選股技巧,選股其實很簡單
- 電腦上永久刪除的檔案怎麼恢復?其實很簡單
- 回收站裡的檔案刪除了怎麼恢復,其實很簡單
- 面試小能手速成,介面測試指令碼如何編寫?其實很簡單面試指令碼
- 什麼是新媒體矩陣運營?運營矩陣其實很簡單矩陣
- 簡陋到極致便成了經典,看似很Low的開羅遊戲其實並不簡單遊戲
- 重灌win10系統方法分享,自己重灌win10系統其實很簡單Win10
- vuex其實超簡單,只需3步Vue
- HTML很簡單?不!HTML
- 常用的自媒體工具有哪些?把文章發到30+平臺其實很簡單
- 淘寶放大鏡的簡單實現,原來道理很簡單
- FIFO 快取演算法很簡單,但也可以聊挺久快取演算法
- 掌握電商資料的4個要點!電商平臺資料分析其實很簡單
- 入門Flink,很簡單
- 2002: 聽說很簡單
- LRU cache快取簡單實現快取
- 爬蟲,其實本就是這麼簡單爬蟲