對於經典演算法,你是否也遇到這樣的情形:學時覺得很清楚,可過陣子就忘了?
本系列文章就嘗試解決這個問題。
研讀那些排序演算法,細品它們的名字,其實都很貼切。
比如桶排序,一提起“桶”,我就想到了垃圾分類。
桶排序就是先分類,即把資料放進相應的桶裡,然後對每個桶進行區域性排序,最後再把桶合併一下就行了。
上圖演示了該演算法的總體流程。分為三步,分類,排序和合並。
該演算法的核心是如何分類,也就是,如何劃分桶並把元素放進相應的桶裡。
它的分類是按區間分類。圖中元素最小值是1,最大值是9,因此所有元素位於屬於區間【1,9】。假設桶(區間)的範圍大小是3,那麼就有3個桶,具體是:【1,3】、【4,6】、【7,9】。
用程式碼體現一下:
let array = [3, 8, 6, 1, 5, 7, 9, 2, 4]
let min = Math.min(...array)
let max = Math.max(...array)
let size = 3
let count = Math.floor((max - min) / size) + 1
let buckets = []
for (let i = 0; i < count; i++) {
buckets.push([])
}
console.log(buckets) // [ [], [], [] ]
複製程式碼
總體來說程式碼比較簡單,需要注意的是count的計算。
桶有了,接下來就是把每個元素歸類。
for (let v of array) {
let num = Math.floor((v - min) / size)
buckets[num].push(v)
}
console.log(buckets) // [ [ 3, 1, 2 ], [ 6, 5, 4 ], [ 8, 7, 9 ] ]
複製程式碼
其中,num表示桶的序號,與count一樣,也是通過偏移量來計算的。因為下標是從0開始的,因此這裡沒有再加1。
關鍵問題解決了,排序和合並就相對簡單了。
排序用其他任一排序演算法都可以,比如資料量不太大的時候,插入排序就比較適合。
因為區間本來就是有序的,合併時只需直接連線這些陣列即可。
let result = []
for (bucket of buckets) {
result.push(...insertionSort(bucket))
}
console.log(result) // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
複製程式碼
至此,桶排序原理和實現已經說完了。檢視完整程式碼:codepen。
其實還有優化的空間,比如min和max可以通過一次迴圈確定出來。另外一點是,我們知道插入排序的思想是把待排序元素插入到已排序序列裡,因此可以在第一步分類時就直接插入就行。
這裡總結一下,桶排序是分散式排序,適合處理大批量資料。需要額外空間,是外部排序。桶排序是否穩定,取決於第二步排序演算法的選擇。時間複雜度是線性級O(n),可以簡單理解:桶的範圍大小是人為指定的,它不隨資料規模變化,如果資料相對均勻分佈,那麼桶的個數就是核心影響因子了。
桶排序,要做到能分分鐘手寫出來,是需要掌握其排序原理的。核心是如何劃分割槽間和元素歸類,一旦理解就容易寫出來,不需要死記硬背的。
希望有所幫助,本文完。
本系列已經發表文章: