詳解排序演算法(四)之計數排序
計數排序
計數排序不是一個比較排序演算法,該演算法於1954年由 Harold H. Seward提出。
01 演算法步驟
-
找到數列的最大值,計為
max
-
新建一個長度為
max + 1
的陣列,計為bucket
-
遍歷數列,在
bucket
中找到值對應的下標,若對應下標裡已有值,值加 1,若無值,將值設定為 1,例如值為5,則找到 bucket 中下標為 5 的位置,即
bucket [5]
,若bucket [5]
不存在,則設定bucket [5]
為1,即bucket [5] = 1
,若bucket [5]
存在,則值加 1,即bucket [5]++
-
遍歷
bucket
count
,下標為index
,往結果陣列中插入count
個元素,元素的值為index
例如bucket [5] = 3
,則count = 3
,index = 5
,往結果陣列中插入 3 個 5。
02 示例
我們取 2, 3, 8, 7, 1, 2, 2, 2, 7, 3, 9, 8, 2, 1, 4, 2, 4, 6, 9, 2 來示範
-
找到最大值為 9
-
新建一個長度為 10 的陣列,[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
-
找到下標 2,將其值置為 1,得 [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
找到下標 3,將其值置為 1,得 [0, 0, 1, 1
找到下標 8,將其值置為 1,得 [0, 0, 1, 1, 0, 0, 0, 0, 1, 0]
找到下標 7,將其值置為 1,得 [0, 0, 1, 1, 0, 0, 0, 1, 1, 0]
找到下標 1,將其值置為 1,得 [0, 1, 1, 1, 0, 0, 0, 1, 1, 0]
找到下標 2,將其內值加 1,得 [0, 1, 2, 1, 0, 0, 0, 1, 1, 0]
找到下標 2,將其內值加 1,得 [0, 1, 3, 1, 0, 0, 0, 1, 1, 0]
找到下標 2,將其內值加 1,得 [0, 1, 4, 1, 0, 0, 0, 1, 1, 0]
找到下標 7,將其內值加 1,得 [0, 1, 4, 1, 0, 0, 0, 2
找到下標 3,將其內值為 1,得 [0, 1, 4, 2, 0, 0, 0, 2, 1, 0]
找到下標 9,將其值置為 1,得 [0, 1, 4, 2, 0, 0, 0, 2, 1, 1]
找到下標 8,將其內值加 1,得 [0, 1, 4, 2, 0, 0, 0, 2, 2, 1]
找到下標 2,將其內值加 1,得 [0, 1, 5, 2, 0, 0, 0, 2, 2, 1]
找到下標 1,將其內值加 1,得 [0, 2, 5, 2, 0, 0, 0, 2, 2, 1]
找到下標 4,將其值置為 1,得 [0, 2, 5, 2, 1, 0, 0, 2, 2, 1]
找到下標 2,將其內值加 1,得 [0, 2, 6, 2, 1, 0, 0, 2, 2, 1]
找到下標 4,將其內值加 1,得 [0, 2, 6, 2, 2, 0, 0, 2, 2, 1]
找到下標 6,將其值置為 1,得 [0, 2, 6, 2, 2, 0, 1, 2, 2, 1]
找到下標 9,將其內值加 1,得 [0, 2, 6, 2, 2, 0, 1, 2, 2, 2]
找到下標 2,將其內值加 1,得 [0, 2, 7, 2, 2, 0, 1, 2, 2, 2]
-
遍歷 [0, 2, 7, 2, 2, 0, 1, 2, 2, 2]
插入 2 個 1,得 [1, 1]
插入 7 個 2,得 [1, 1, 2, 2, 2, 2, 2, 2, 2]
插入 2 個 3,得 [1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3]
插入 2 個 4,得 [1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4]
插入 1 個 6,得 [1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 6]
插入 2 個 7,得 [1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 6, 7, 7]
插入 2 個 8,得 [1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 6, 7, 7, 8, 8]
插入 2 個 9,得 [1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 6, 7, 7, 8, 8, 9, 9]
排序完成。
03 動態圖
04 javascript程式碼
function countSort(arr) {
const max = getMax(arr), // 陣列中最大值
bucketLen = max + 1
bucket = new Array(bucketLen)
let sortedIndex = 0
for (let i = 0; i < arr.length; i++) {
if (!bucket[arr[i]]) {
bucket[arr[i]] = 0
}
bucket[arr[i]]++
}
for (let j = 0; j < bucketLen; j++) {
while(bucket[j] > 0) {
arr[sortedIndex++] = j
bucket[j]--
}
}
return arr
}
// 獲取陣列最大值
function getMax (arr) {
let max = arr[0]
arr.forEach(e => {
max = e > max ? e : max
})
return max
}
優化版 —— 基於最小值的計數排序
經過上面的學習,我們已經掌握了計數排序的過程。但上面的演算法有問題嗎?當然有!!!
我們不妨舉以下兩個例子
- 取數列 101, 99, 99, 100 ,按照上面的演算法,所需陣列長度為 102,但實際上,我們只用到了 3 個位置,空間大大浪費。
- 假設數列中存在負數,如 -1, 10, 11,按照以上演算法,陣列長度為 12,可適配的值的範圍為 0 - 11,但 -1 顯然不在這個範圍內
基於以上問題,優化版 —— 基於最小值的計數排序 便應運而生。
01 演算法步驟
-
找到數列的最小值、最大值,分別計為
min
、max
-
新建一個長度為
max + 1
的陣列,計為bucket
-
遍歷數列,在
bucket
中找到值 - min
對應的下標,若對應下標裡已有值,值加 1,若無值,將值設定為 1,例如值為5,
min = 2
,則找到 bucket 中下標為 3 的位置,即bucket [3]
,若bucket [3]
不存在,則設定bucket [3]
為 1,即bucket [3] = 1
,若bucket [3]
存在,則值加 1,即bucket [3]++
-
遍歷
bucket
,對於存在值的元素,設其值為count
,下標為index
,往結果陣列中插入count
個元素,元素的值為min + index
例如bucket [3] = 3
,min = 2
,則count = 3
,index = 3
,min + index = 5
,往結果陣列中插入 3 個 5。
02 示例
我們取 101, 99, 99, 100 來示範
-
找到最小值為 99,最大值 101,101 - 99 = 2
-
新建一個長度為 3 的陣列,[0, 0, 0]
-
找到下標 101 - 99 = 2,將其值置為 1,得 [0, 0, 1]
找到下標 99 - 99 = 0,將其值置為 1,得 [1, 0, 1]
找到下標 99 - 99 = 0,將其內值加 1,得 [2, 0, 1]
找到下標 100 - 99 = 1,將其值置為 1,得 [2, 1, 1]
可以看到,優化後的演算法用到的陣列長度為 3,而優化前的陣列長度為 102
-
遍歷 [2, 1, 1]
插入 2 個 99 + 0 = 99,得 [99, 99]
插入 1 個 99 + 1 = 100,得 [99, 99, 100]
插入 1 個 99 + 2 = 101,得 [99, 99, 100, 101]
javascript程式碼
function countSort(arr) {
const max = getMax(arr), // 陣列中最大值
min = getMin(arr), // 陣列中最小值,以其作為基數
bucketLen = max - min + 1
bucket = new Array(bucketLen)
let sortedIndex = 0
for (let i = 0; i < arr.length; i++) {
const index = arr[i] - min // 插入位置的下標
if (!bucket[index]) {
bucket[index] = 0
}
bucket[index]++
}
for (let j = 0; j < bucketLen; j++) {
while(bucket[j] > 0) {
arr[sortedIndex++] = min + j
bucket[j]--
}
}
return arr
}
// 獲取陣列最大值
function getMax (arr) {
let max = arr[0]
arr.forEach(e => {
max = e > max ? e : max
})
return max
}
// 獲取陣列最小值
function getMin (arr) {
let min = arr[0]
arr.forEach(e => {
min = e < min ? e : min
})
return min
}
總結
01 複雜度及穩定性
排序演算法 | 時間複雜度(平均) | 時間複雜度(最壞) | 時間複雜度(最好) | 空間複雜度 | 穩定性 |
---|---|---|---|---|---|
計數排序 | O(n + k) | O(n + k) | O(n + k) | O(k) | 穩定 |
其中 k 為數列不重複元素的個數
02 使用場景
- 適用場景:元素為整數且排列密集
- 不適用場景:元素存在小數或排列不密集