豐富圖例講解十大經典排序算法 | 面試必備

面試官問:你會三路快排嗎?git

我:github

...面試


對比

關於時間複雜度:算法

  1. 平方階 (O(n**2)) 排序 各種簡單排序:直接插入、直接選擇和冒泡排序。
  2. 線性對數階 (O(nlog2n)) 排序: 快速排序、堆排序和歸併排序;
  3. O(n1+§)) 排序,§ 是介於 0 和 1 之間的常數。 希爾排序
  4. 線性階 (O(n)) 排序 基數排序,此外還有桶、箱排序。

原地排序:特指空間複雜度是 O(1) 的排序算法。shell

穩定性:若是待排序的序列中存在值相等的元素,通過排序以後,相等元素之間原有的前後順序不變。api

冒泡排序

冒泡排序(英語:Bubble Sort)又稱爲泡式排序,是一種簡單的排序算法。它重複地走訪過要排序的數列,一次比較兩個元素,若是他們的順序錯誤就把他們交換過來。走訪數列的工做是重複地進行直到沒有再須要交換,也就是說該數列已經排序完成。這個算法的名字由來是由於越小的元素會經由交換慢慢「浮」到數列的頂端。數組

冒泡排序對 n 個項目須要 O(n**2) 的比較次數,且能夠原地排序。儘管這個算法是最簡單瞭解和實現的排序算法之一,但它對於包含大量的元素的數列排序是很沒有效率的。微信

冒泡排序是與插入排序擁有相等的運行時間,可是兩種算法在須要的交換次數卻很大地不一樣。在最壞的狀況,冒泡排序須要 O(n**2) 次交換,而插入排序只要最多 O(n) 交換。冒泡排序的實現(相似下面)一般會對已經排序好的數列拙劣地運行(O(n ** 2)),而插入排序在這個例子只須要 O(n) 個運算。所以不少現代的算法教科書避免使用冒泡排序,而用插入排序取代之。冒泡排序若是能在內部循環第一次運行時,使用一個旗標來表示有無須要交換的可能,也能夠把最優狀況下的複雜度下降到 O(n) 。在這個狀況,已經排序好的數列就無交換的須要。若在每次走訪數列時,把走訪順序反過來,也能夠稍微地改進效率。有時候稱爲雞尾酒排序,由於算法會從數列的一端到另外一端之間穿梭往返。markdown

冒泡排序算法的運做以下:數據結構

  1. 比較相鄰的元素。若是第一個比第二個大,就交換他們兩個。
  2. 對每一對相鄰元素做一樣的工做,從開始第一對到結尾的最後一對。這步作完後,最後的元素會是最大的數。
  3. 針對全部的元素重複以上的步驟,除了最後一個。
  4. 持續每次對愈來愈少的元素重複上面的步驟,直到沒有任何一對數字須要比較。
export function bubbleSort(arr: number[]) {
  const length = arr.length
  if (length <= 1) return arr
  for (let i = 0; i < length; i++) {
    let changed: boolean = false // 沒有數據交換則表示已經有序了
    for (let j = 0; j < length - 1 - i; j++) {
      if (arr[j] > arr[j + 1]) {
        swap(arr, j, j + 1)
        changed = true
      }
    }
    if (!changed) break
  }
  return arr
}
複製代碼

雞尾酒排序

export function cocktailSort(arr: number[]) {
  const len = arr.length
  for (let i = 0; i < len / 2; i++) {
    let start: number = 0
    let end: number = len - 1
    for (let j = start; j < end; j++) {
      if (arr[j] > arr[j + 1]) swap(arr, j, j + 1)
    }
    end--
    for (let j = end; j > start; j--) {
      if (arr[j] < arr[j - 1]) swap(arr, j - 1, j)
    }
    start++
  }
  return arr
}
複製代碼

冒泡排序

冒泡排序

冒泡排序2

雞尾酒排序

雞尾酒排序

選擇排序

選擇排序(Selection sort)是一種簡單直觀的排序算法。它的工做原理以下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,而後,再從剩餘未排序元素中繼續尋找最小(大)元素,而後放到已排序序列的末尾。以此類推,直到全部元素均排序完畢。

選擇排序的主要優勢與數據移動有關。若是某個元素位於正確的最終位置上,則它不會被移動。選擇排序每次交換一對元素,它們當中至少有一個將被移到其最終位置上,所以對 n 個元素的表進行排序總共進行至多 n - 1 次交換。在全部的徹底依靠交換去移動元素的排序方法中,選擇排序屬於很是好的一種。

export function selectionSort(arr: number[]) {
  const length = arr.length
  if (length <= 1) return arr
  for (let i = 0; i < length; i++) {
    let min = i
    for (let j = i + 1; j < length; j++) {
      if (arr[j] < arr[min]) {
        min = j
      }
    }
    swap(arr, i, min)
  }
  return arr
}
複製代碼

選擇排序1

選擇排序2

選擇排序3

插入排序

插入排序(英語:Insertion Sort)是一種簡單直觀的排序算法。它的工做原理是經過構建有序序列,對於未排序數據,在已排序序列中從後向前掃描,找到相應位置並插入。插入排序在實現上,一般採用 in-place 排序(即只需用到 O(1) 的額外空間的排序),於是在從後向前掃描過程當中,須要反覆把已排序元素逐步向後挪位,爲最新元素提供插入空間。

Insertion Sort 和打撲克牌時,從牌桌上逐一拿起撲克牌,在手上排序的過程相同。

通常來講,插入排序都採用 in-place 在數組上實現。具體算法描述以下:

  1. 從第一個元素開始,該元素能夠認爲已經被排序
  2. 取出下一個元素,在已經排序的元素序列中從後向前掃描
  3. 若是該元素(已排序)大於新元素,將該元素移到下一位置
  4. 重複步驟 3,直到找到已排序的元素小於或者等於新元素的位置
  5. 將新元素插入到該位置後
  6. 重複步驟 2~5
export function insertionSort(arr: number[]) {
  const length = arr.length
  if (length <= 1) return arr
  for (let i = 1; i < length; i++) {
    const cur = arr[i]
    let j = i - 1
    for (; j >= 0; j--) {
      if (arr[j] > cur) {
        arr[j + 1] = arr[j]
      } else {
        break
      }
    }
    arr[j + 1] = cur
  }

  return arr
}
複製代碼

or

export function insertionSort2(arr: number[]) {
  const len = arr.length
  for (let i = 1; i < len; i++) {
    for (let j = i - 1; j >= 0; j--) {
      if (arr[j] > arr[j + 1]) {
        // 這裏是更改兩個元素,因此比上面的方法效率低
        swap(arr, j + 1, j)
      } else {
        break
      }
    }
  }
  return arr
}
複製代碼

插入排序1

插入排序2

插入排序3

快速排序

快速排序,快速排序(英語:Quicksort),又稱劃分交換排序(partition-exchange sort),簡稱快排,一種排序算法,最先由東尼·霍爾提出。在平均情況下,排序 n 個項目要 O(nlogn) (大 O 符號)次比較。在最壞情況下則須要 O(n**2) 次比較,但這種情況並不常見。事實上,快速排序 O(nlogn) 一般明顯比其餘算法更快,由於它的內部循環(inner loop)能夠在大部分的架構上頗有效率地達成。

快速排序使用 分治法(Divide and conquer) 策略來把一個序列(list)分爲較小和較大的 2 個子序列,而後遞歸地排序兩個子序列。

步驟爲:

  1. 挑選基準值:從數列中挑出一個元素,稱爲「基準」(pivot),
  2. 分割:從新排序數列,全部比基準值小的元素擺放在基準前面,全部比基準值大的元素擺在基準後面(與基準值相等的數能夠到任何一邊)。在這個分割結束以後,對基準值的排序就已經完成,
  3. 遞歸排序子序列:遞歸地將小於基準值元素的子序列和大於基準值元素的子序列排序。

遞歸到最底部的判斷條件是數列的大小是零或一,此時該數列顯然已經有序。

選取基準值有數種具體方法,此選取方法對排序的時間性能有決定性影響。

1 普通快排

function partition(arr: number[], left: number, right: number): number {
  let pivot: number = left // 默認從最左邊開始,有優化空間
  let index = pivot + 1
  for (let i = index; i <= right; i++) {
    if (arr[i] < arr[pivot]) {
      swap(arr, i, index)
      index++
    }
  }
  swap(arr, pivot, index - 1)
  return index - 1
}

export function quickSort(arr: number[], l?: number, r?: number) {
  const len = arr.length
  const left: number = typeof l === 'number' ? l : 0
  const right: number = typeof r === 'number' ? r : len - 1
  let partitionIndex = 0
  if (left < right) {
    partitionIndex = partition(arr, left, right)
    quickSort(arr, left, partitionIndex - 1)
    quickSort(arr, partitionIndex + 1, right)
  }
  return arr
}
複製代碼

2 左右指針快排

function partition(arr: number[], left: number, right: number): number {
  let l: number = left // 默認從最左邊開始,有優化空間
  let r: number = right
  const target: number = arr[left]

  while (l < r) {
    while (arr[r] >= target && r > l) {
      r--
    }
    while (arr[l] <= target && l < r) {
      l++
    }
    swap(arr, l, r)
  }

  if (l !== left) {
    swap(arr, l, left)
  }

  return l
}

export function quickSort2(arr: at, l?: number, r?: number) {
  const len = arr.length
  const left: number = typeof l === 'number' ? l : 0
  const right: number = typeof r === 'number' ? r : len - 1
  let partitionIndex = 0
  if (left < right) {
    partitionIndex = partition(arr, left, right)
    quickSort2(arr, left, partitionIndex - 1)
    quickSort2(arr, partitionIndex + 1, right)
  }
  return arr
}
複製代碼

3 三路快排

function partion(arr: at, l: number, r: number) {
  // 基準數選取區間的第一個值
  let v = arr[l]
  let lt = l
  let gt = r + 1

  // 下面的循環很差理解
  // i 和 gt 都在變化,gt 向左移動能夠不影響 i,lt 增加會把等於v的項轉移到 i,因此須要 i++
  for (let i = l + 1; i < gt; ) {
    if (arr[i] === v) {
      // lt 和 i 在這裏拉開差距
      i++
    } else if (arr[i] > v) {
      swap(arr, gt - 1, i)
      gt--
    } else {
      swap(arr, lt + 1, i)
      lt++
      i++
    }
  }

  swap(arr, l, lt) // arr[lt] === v
  lt--
  return { lt, gt }
}

export function quickSort3(arr: at, l?: number, r?: number) {
  const len = arr.length
  const left: number = typeof l === 'number' ? l : 0
  const right: number = typeof r === 'number' ? r : len - 1
  if (left >= right) return
  let { lt, gt } = partion(arr, left, right)
  quickSort3(arr, l, lt)
  quickSort3(arr, gt, r)
  return arr
}
複製代碼

快速排序

快速排序

希爾排序(shell sort)

希爾排序,也稱遞減增量排序算法,是插入排序的一種更高效的改進版本。希爾排序是非穩定排序算法。

希爾排序是基於插入排序的如下兩點性質而提出改進方法的:

  1. 插入排序在對幾乎已經排好序的數據操做時,效率高,便可以達到線性排序的效率
  2. 但插入排序通常來講是低效的,由於插入排序每次只能將數據移動一位

希爾排序經過將比較的所有元素分爲幾個區域來提高插入排序的性能。這樣可讓一個元素能夠一次性地朝最終位置前進一大步。而後算法再取愈來愈小的步長進行排序,算法的最後一步就是普通的插入排序,可是到了這步,需排序的數據幾乎是已排好的了(此時插入排序較快)。

export function shellSort(arr: number[]) {
  const length: number = arr.length
  let i, j
  // 調整 gap
  for (let gap = length >> 1; gap > 0; gap >>= 1) {
    // 按區間插排
    for (i = gap; i < length; i++) {
      let temp: number = arr[i]
      // 從當前位置往左按區間掃描
      for (j = i - gap; j >= 0 && arr[j] > temp; j -= gap) {
        arr[j + gap] = arr[j]
      }
      arr[j + gap] = temp
    }
  }
  return arr
}
複製代碼

or

export function shellSort2(arr: number[]) {
  const length: number = arr.length
  let i, j
  // 調整 gap
  for (let gap = length >> 1; gap > 0; gap >>= 1) {
    // 按區間插排
    for (i = gap; i < length; i++) {
      // 從當前位置往左按區間掃描
      for (j = i - gap; j >= 0 && arr[j] > arr[j + gap]; j -= gap) {
        // 這裏是更改兩個元素,因此比上面的方法效率低
        swap(arr, j, j + gap)
      }
    }
  }
  return arr
}
複製代碼

希爾排序

歸併排序(merge sort)

歸併排序(英語:Merge sort,或 mergesort),是建立在歸併操做上的一種有效的排序算法,效率爲 O(nlogn)。1945 年由約翰·馮·諾伊曼首次提出。該算法是採用 分治法(Divide and Conquer) 的一個很是典型的應用,且各層分治遞歸能夠同時進行。

採用分治法:

  1. 分割:遞歸地把當前序列平均分割成兩半。
  2. 集成:在保持元素順序的同時將上一步獲得的子序列集成到一塊兒(歸併)。

歸併操做(merge),也叫歸併算法,指的是將兩個已經排序的序列合併成一個序列的操做。歸併排序算法依賴歸併操做。

歸併排序有兩種思路:

遞歸法(Top-down)

  1. 申請空間,使其大小爲兩個已經排序序列之和,該空間用來存放合併後的序列
  2. 設定兩個指針,最初位置分別爲兩個已經排序序列的起始位置
  3. 比較兩個指針所指向的元素,選擇相對小的元素放入到合併空間,並移動指針到下一位置
  4. 重複步驟 3 直到某一指針到達序列尾
  5. 將另外一序列剩下的全部元素直接複製到合併序列尾
function merge(lArr: number[], rArr: number[]) {
  const result: number[] = []
  while (lArr.length && rArr.length) {
    if (lArr[0] < rArr[0]) {
      result.push(<number>lArr.shift())
    } else {
      result.push(<number>rArr.shift())
    }
  }
  while (lArr.length) {
    result.push(<number>lArr.shift())
  }
  while (rArr.length) {
    result.push(<number>rArr.shift())
  }
  return result
}

function merge2(lArr: number[], rArr: number[]) {
  const result: number[] = []
  let lLen = lArr.length
  let rLen = rArr.length
  let i = 0
  let j = 0
  while (i < lLen && j < rLen) {
    if (lArr[i] < rArr[j]) result.push(lArr[i++])
    else result.push(rArr[j++])
  }

  while (i < lLen) result.push(lArr[i++])
  while (j < rLen) result.push(rArr[j++])

  return result
}
複製代碼

迭代法(Bottom-up)

原理以下(假設序列共有 n 個元素):

  1. 將序列每相鄰兩個數字進行歸併操做,造成 ceil(n/2) 個序列,排序後每一個序列包含兩/一個元素
  2. 若此時序列數不是 1 個則將上述序列再次歸併,造成 ceil(n/4) 個序列,每一個序列包含四/三個元素
  3. 重複步驟 2,直到全部元素排序完畢,即序列數爲 1
export function mergeSort2(arr: number[]): number[] {
  const len = arr.length
  for (let sz = 1; sz < len; sz *= 2) {
    for (let i = 0; i < len - sz; i += 2 * sz) {
      const start = i
      const mid = i + sz - 1
      const end = Math.min(i + 2 * sz - 1, len - 1)
      merge(arr, start, mid, end)
    }
  }
  return arr
}

function merge(arr: number[], start: number, mid: number, end: number) {
  let i = start
  let j = mid + 1
  const tmp = []
  let k = start
  for (let w = start; w <= end; w++) {
    tmp[w] = arr[w]
  }
  while (i < mid + 1 && j < end + 1) {
    if (tmp[i] < tmp[j]) arr[k++] = tmp[i++]
    else arr[k++] = tmp[j++]
  }
  while (i < mid + 1) arr[k++] = tmp[i++]
  while (j < end + 1) arr[k++] = tmp[j++]
}
複製代碼

歸併排序
歸併排序
排序一組數字

堆排序(heap sort)

一般堆是經過一維數組來實現的。在數組起始位置爲 0 的情形中:

  1. 父節點 i 的左子節點在位置 2 * i + 1
  2. 父節點 i 的右子節點在位置 2 * i + 2
  3. 子節點 i 的父節點在位置 floor((i -1) / 2)

堆的操做

在堆的數據結構中,堆中的最大值老是位於根節點(在優先隊列中使用堆的話堆中的最小值位於根節點)。堆中定義如下幾種操做:

  1. 最大堆調整(Max Heapify):將堆的末端子節點做調整,使得子節點永遠小於父節點
  2. 建立最大堆(Build Max Heap):將堆中的全部數據從新排序
  3. 堆排序(HeapSort):移除位在第一個數據的根節點,並作最大堆調整的遞歸運算
function heapifyMax(arr: at, i: number, len: number) {
  const left = 2 * i + 1
  const right = 2 * i + 2
  let max = i

  if (left < len && arr[left] > arr[max]) {
    max = left
  }

  if (right < len && arr[right] > arr[max]) {
    max = right
  }

  if (max !== i) {
    swap(arr, max, i)
    heapifyMax(arr, max, len)
  }
}

function heapifyMin(arr: at, i: number, len: number) {
  const left = 2 * i + 1
  const right = 2 * i + 2
  let min = i

  if (left < len && arr[left] < arr[min]) {
    min = left
  }

  if (right < len && arr[right] < arr[min]) {
    min = right
  }

  if (min !== i) {
    swap(arr, min, i)
    heapifyMin(arr, min, len)
  }
}

// 構建大頂堆
function buildMaxHeap(arr: at) {
  const len = arr.length
  for (let i = Math.floor(len / 2); i >= 0; i--) {
    heapifyMax(arr, i, len)
  }
}

// 構建小頂堆
function buildMinHeap(arr: at) {
  const len = arr.length
  for (let i = Math.floor(len / 2); i >= 0; i--) {
    heapifyMin(arr, i, len)
  }
}

// asc 爲 true 表示從小到大,false 爲從大到小
export function heapSort(arr: at, asc: boolean = true) {
  if (asc) {
    buildMaxHeap(arr)
    const len = arr.length
    for (let i = len - 1; i > 0; i--) {
      swap(arr, 0, i)
      heapifyMax(arr, 0, i)
    }
  } else {
    buildMinHeap(arr)
    const len = arr.length
    for (let i = len - 1; i > 0; i--) {
      swap(arr, 0, i)
      heapifyMin(arr, 0, i)
    }
  }
  return arr
}
複製代碼

堆排序

計數排序

限定爲非負數

計數排序(Counting sort)是一種穩定的線性時間排序算法。該算法於 1954 年由 Harold H. Seward 提出。計數排序使用一個額外的數組 C ,其中第 i 個元素是待排序數組 A 中值等於 i 的元素的個數。而後根據數組 C 來將 A 中的元素排到正確的位置。

當輸入的元素是 n 個 0 到 k 之間的整數時,它的運行時間是 t(n+k)。計數排序不是比較排序,排序的速度快於任何比較排序算法

因爲用來計數的數組 C 的長度取決於待排序數組中數據的範圍(等於待排序數組的最大值與最小值的差加上 1),這使得計數排序對於數據範圍很大的數組,須要大量時間和內存。例如:計數排序是用來排序 0 到 100 之間的數字的最好的算法,可是它不適合按字母順序排序人名。可是,計數排序能夠用在基數排序算法中,可以更有效的排序數據範圍很大的數組。

算法的步驟以下:

  1. 找出待排序的數組中最大和最小的元素
  2. 統計數組中每一個值爲 i 的元素出現的次數,存入數組 C 的第 i 項
  3. 全部的計數累加(從 C 中的第一個元素開始,每一項和前一項相加)
  4. 反向填充目標數組:將每一個元素 i 放在新數組的第 C[i]項,每放一個元素就將 C[i] 減去 1
export function countingSort(arr: at) {
  const bucket: at = []
  const len = arr.length
  // 數組下標的遊標
  let sortIndex: number = 0

  for (let i = 0; i < len; i++) {
    if (bucket[arr[i]]) {
      bucket[arr[i]]++
    } else {
      // 數組的下標不能爲負數,因此計數排序限制只能排序天然數
      bucket[arr[i]] = 1
    }
  }

  for (let j = 0; j < bucket.length; j++) {
    while (bucket[j]) {
      arr[sortIndex++] = j
      bucket[j]--
    }
  }
  return arr
}
複製代碼

計數排序

基數排序

基數排序(英語:Radix sort)是一種非比較型整數排序算法,其原理是將整數按位數切割成不一樣的數字,而後按每一個位數分別比較。因爲整數也能夠表達字符串(好比名字或日期)和特定格式的浮點數,因此基數排序也不是隻能使用於整數。基數排序的發明能夠追溯到 1887 年赫爾曼·何樂禮在打孔卡片製表機(Tabulation Machine)上的貢獻。

它是這樣實現的:將全部待比較數值(正整數)統一爲一樣的數字長度,數字較短的數前面補零。而後,從最低位開始,依次進行一次排序。這樣從最低位排序一直到最高位排序完成之後,數列就變成一個有序序列。

基數排序的方式能夠採用 LSD(Least significant digital)或 MSD(Most significant digital),LSD 的排序方式由鍵值的最右邊開始,而 MSD 則相反,由鍵值的最左邊開始。

基數排序的時間複雜度是 O(k*n),其中 n 是排序元素個數,k 是數字位數。這不是說這個時間複雜度必定優於 O(nlogn),k 的大小取決於數字位的選擇(好比比特位數),和待排序數據所屬數據類型的全集的大小;k 決定了進行多少輪處理,而 n 是每輪處理的操做數目。

export function radixSort(arr: at): at {
  const len = arr.length
  const max = Math.max(...arr)
  let buckets: at[] = []
  let digit = `${max}`.length
  let start = 1
  let res: at = arr.slice()
  while (digit > 0) {
    start *= 10
    for (let i = 0; i < len; i++) {
      const j = res[i] % start
      if (buckets[j] === void 0) {
        buckets[j] = []
      }
      buckets[j].push(res[i])
    }
    res = []
    for (let j = 0; j < buckets.length; j++) {
      buckets[j] && (res = res.concat(buckets[j]))
    }
    buckets = []
    digit--
  }
  return res
}
複製代碼

基數排序

桶排序、箱排序

桶排序(Bucket sort)或所謂的箱排序,是一個排序算法,工做的原理是將數組分到有限數量的桶裏。每一個桶再個別排序(有可能再使用別的排序算法或是以遞歸方式繼續使用桶排序進行排序)。桶排序是鴿巢排序的一種概括結果。當要被排序的數組內的數值是均勻分配的時候,桶排序使用線性時間(O(n))。但桶排序並非比較排序,他不受到 O(nlogn)下限的影響。

桶排序如下列程序進行:

  1. 設置一個定量的數組看成空桶子。
  2. 尋訪序列,而且把項目一個一個放到對應的桶子去。
  3. 對每一個不是空的桶子進行排序。
  4. 從不是空的桶子裏把項目再放回原來的序列中。
export function bucketSort(arr: at, size: number = 5) {
  const len = arr.length
  const max = Math.max(...arr)
  const min = Math.min(...arr)
  const bucketSize = Math.floor((max - min) / size) + 1
  const bucket: at[] = []
  const res: at = []

  for (let i = 0; i < len; i++) {
    const j = Math.floor((arr[i] - min) / bucketSize)
    !bucket[j] && (bucket[j] = [])
    bucket[j].push(arr[i])
    let l = bucket[j].length
    while (l > 0) {
      // 每一個桶內部要進行排序
      // 冒泡已經很快了,其實只有一個元素須要肯定本身的位置
      bucket[j][l] < bucket[j][l - 1] && swap(bucket[j], l, l - 1)
      // 不要直接這麼一個排序,bucket[j]內部都是有序的,只有最後一個是無序的
      // bucket[j].sort((a, b) => a - b)
      l--
    }
  }

  // 每一個桶內部數據已是有序的
  // 將桶內數組拼接起來便可
  for (let i = 0; i < bucket.length; i++) {
    const l = bucket[i] ? bucket[i].length : 0
    for (let j = 0; j < l; j++) {
      res.push(bucket[i][j])
    }
  }
  return res
}
複製代碼

桶排序
圖片來自:五分鐘學算法

簡單測下性能

性能測試代碼

測試條件是

for (let i = 0; i < 100000; i++) {
  arr.push(Math.floor(Math.random() * 10000))
}
複製代碼

測試結果

我測了不少遍,發現計數排序的速度是絕對的第一,固然空間上落後了,若是是大量重複度高,間距不大的值能夠考慮。普通快排的速度也是很是快,基數、桶(可能與桶內排序算法速度有關)雖然是非比較類排序,可是速度上並不佔優點,希爾、堆排序速度也是很是快,而選擇、冒泡排序則很是緩慢。

參考


歡迎你們關注個人掘金和公衆號,算法、TypeScript、React 及其生態源碼按期講解。


推薦一個很是好用的多平臺編輯器, 支持直接複製到微信、知乎、掘金、頭條等平臺,樣式可自定義,預約義的也很好看。

相關文章
相關標籤/搜索