手寫算法並記住它:計數排序

對於經典算法,你是否也遇到這樣的情形:學時以爲很清楚,可過陣子就忘了?javascript

本系列文章就嘗試解決這個問題。java

研讀那些排序算法,細品它們的名字,其實都很貼切。算法

好比計數排序,所謂「計數」,就是數一數,統計每一個元素重複出現的次數。數組

上圖演示了該算法的整體流程。分爲兩步:查和排。post

首先查一查每一個元素都出現了多少次,好比元素0出現了1一次,元素1出現了一次,元素2出現了3次等。ui

都統計好了,而後排序的過程就簡單了,從小到大按順序填充數組便可,出現幾回就填充幾回就行了。「從小到大」這個詞語,就體現了排序的過程。spa

在我看來,計數排序是全部排序算法中最簡單的,也是最符合直覺的算法。查和排,這兩者都很容易實現。3d

查,實現以下:code

let array = [3, 2, 1, 2, 3, 2, 0, 4]
let counts = []
for (let v of array) {
  counts[v] = (counts[v] || 0) + 1
}
console.log(counts) // [1, 1, 3, 2, 1]
複製代碼

其中counts數組是統計結果,用其下標表示待排數組的元素。其長度爲5(待排數組的最大值加1)。cdn

排,實現以下:

let result = []
for (let i = 0; i < counts.length; i++) {
  let count = counts[i]
  while(count > 0) {
    result.push(i)
    count--
  }
}
console.log(result) // [0, 1, 2, 2, 2, 3, 3, 4]
複製代碼

上述代碼,result是新數組,固然你也可使用array來填充,那樣就須要有一個變量來記錄排到第幾個位置了,能夠查看文末的完整實現連接。

從算法具體過程能夠看出,計數排序適合整數排序。這裏有個問題,假如數值中有負數怎麼辦?

解決之道,也容易想到,咱們先遍歷一遍求出數組中的最小值,以此做爲偏移量就好。

假如數據是[-3, -2, -1, -2, -3, -2, 0, -4],其中數據中數組的最小值是-4,最大值是0。咱們使用counts的第0個下標表示最小值就好了。

let array = [-3, -2, -1, -2, -3, -2, 0, -4]
let counts = [], result = []
let min = Math.min(...array)
for (let v of array) {
  counts[v-min] = (counts[v-min] || 0) + 1
}
for (let i = 0; i < counts.length; i++) {
  let count = counts[i]
  while(count > 0) {
    result.push(i + min)
    count--
  }
}
console.log(result) // [-4, -3, -3, -2, -2, -2, -1, 0]
複製代碼

統計時要減去偏移量,排序時要加回來的。另外,能夠看出counts的長度是最大值和最小值的差值加1。

這種實現方式,也避免了另一個問題,數據集中空間浪費情形。好比數據都介於900~1000的,那麼counts的長度只須要101就夠了。

至此,計數排序原理和實現已經說完了。

查看完整代碼:codepen

這裏總結一下,計數排序適合整數排序,時間複雜度爲O(n+k)。簡單說明一下爲啥是O(n+k)。這裏使用了兩層循環,外層由counts的length——待排數組最值之差(記爲k)——決定的,而while循環次數是count決定的,而全部count之和正好爲array的length(記爲n)。另外關於空間的使用,開篇實現方式的空間複雜度爲O(n+k),完整代碼裏的實現的空間複雜度爲O(k)。可見當k特別大時,將會使用不少空間。

計數排序,要作到能分分鐘手寫出來,是須要掌握其排序原理的。先統計出現個數,而後從小到大輸出便可,一旦理解就容易寫出來,不須要死記硬背的。

但願有所幫助,本文完。



本系列已經發表文章:

相關文章
相關標籤/搜索