手寫算法並記住它:快速排序(最易理解版)

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

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

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

好比快速排序,一個快字就能體現出其價值,於是它是用得最多的。數組

由於它相對難一些,本系列將分兩篇文章講解它。函數

上一篇是5行代碼實現版本。而本篇是原地排序算法。post

快速排序這個名字是針對其性能來起的,但很難讓人作到見名知意。性能

因此,我給它從新起了個名字:歸分排序。ui

與歸併算法同樣,歸分算法也是分而治之算法,講究分、歸、並。歸併的重頭戲在於如何去合併,快排的重頭戲在於如何去劃分。spa

上圖中,先把數組按最後一個元素4做爲分界點,把數組一分爲三。除了分界點以外,左子部分全是小於等於4的,右子部分全是大於4的,它們能夠進一步遞歸排序。由於是原地排序(不須要額外空間),所以不需歸併那種合併操做。翻譯

其中,歸相對容易些,該算法的核心是:如何把數組按分界點一分爲三

各個教程的實現方式不一,這裏我介紹一個最容易理解的方式。

具體過程是這樣的,選取最後一個元素爲分界點,而後遍歷數組找小於等於分界點的元素,而後往數組前面交換。好比:

上圖中,咱們按順序找小於等於4的元素,共一、二、三、4。而後分別與數組的前4個元素交換便可,結果天然是一分爲三。

是否是很是容易理解的思路?快排也不難學嘛。

咱們用JS實現一遍:

let array = [7, 1, 6, 5, 3, 2, 4]
let j = 0
let pivot = array[array.length - 1]
for (let i = 0; i < array.length; i++) {
  if (array[i] <= pivot) {
    swap(array, i, j++)
  }
}
console.log(array) // [ 1, 3, 2, 4, 7, 6, 5 ]
複製代碼

其中swap函數封裝了兩個元素如何交換:

function swap(array, i, j) {
  [array[i], array[j]] = [array[j], array[i]]
}
複製代碼

進一步封裝成函數:

function partition(array, start, end) {
  let j = start
  let pivot = array[end]
  for (let i = start; i <= end; i++) {
    if (array[i] <= pivot) {
      swap(array, i, j++)
    }
  }
  return j - 1
}
複製代碼

start和end表示數組起止下標。最後返回的j-1是分界點的位置。

接下來就須要遞歸處理左子部分和右子部分了。

對於遞歸,雖然它不符合線性思惟,但其實也沒啥難的。

只要有遞歸步驟(遞歸公式),很容翻譯成代碼的。

咱們再回憶一下快排算法的步驟:

  1. 數組分紅三部分left、pivot、right,使left<=pivot,right>pivot。
  2. 遞歸處理left
  3. 遞歸處理right

輕鬆翻譯成代碼:

function quickSort(array, start = 0, end = array.length -1) {
  let pivotIndex = partition(array, start, end)
  quickSort(array, start, pivotIndex - 1)
  quickSort(array, pivotIndex + 1, end)
  return array
}
複製代碼

遞歸是自身調用自身,不能無限次的調用下去,所以須要有遞歸出口(初始條件)。

它的遞歸出口是,當數組元素個數爲小於2時,就是已是排好序的,不須要再遞歸調用了。

所以須要在前面加入代碼:

if (end - start < 1) return array
複製代碼

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

快排的算法主要在於partition函數的實現,不一樣教程的實現方式都不同,這個須要注意一下。

其時間複雜度平均是O(nlogn)。最壞情形是,假如待排的數組已是排好序的,該算法將退化成O(n^2)級的。此時能夠經過合理的分區點選擇來避免。常見策略有選中間、隨機選、三選一等。假如這裏咱們隨機選一個分區點,再與最後的元素交換,就能大機率避免最壞情形的出現。查看完整代碼:codepen

這裏總結一下,快速排序是原地算法,不須要額外空間,但遞歸是須要空間的的(至關於手動維護個調用棧),整體空間複雜度是O(logn)。相等元素可能會交換先後順序,於是不是穩定排序(由於交換)。時間複雜度爲O(nlogn)。

快速排序,要作到能分分鐘手寫出來,是須要掌握其排序原理的。關鍵在於,如何按照分界點把數組一分爲三。至於遞歸,只要能說清楚遞歸步驟和出口,就能很容易寫出來,不須要死記硬背的。

但願有所幫助,本文完。



本系列已經發表文章:

相關文章
相關標籤/搜索