一文全面了解JavaScript最常見的十種排序算法
在前端面試或日常開發中,排序算法是既基礎又高頻的知識點內容之一。常常會出現在開發面試中,你可能在處理表格數據、排行榜、過濾器等功能時,都遇到過“需要排序”的情況。而選擇哪種排序算法,往往會影響性能與穩定性的平衡。
為了幫助你徹底吃透排序算法,今天這篇文章,我幫大家系統梳理了一下了JavaScript 中最常見的 10 種排序方法,每種算法都包含核心原理,應用場景,時間&空間復雜度等相關內容。不管你是準備面試,還是想知識查漏補缺,這份“排序算法全家桶”都值得收藏!
下面我們開始今天的內容吧。
1. 冒泡排序(Bubble Sort)
原理:每輪比較相鄰兩個元素,若順序錯誤就交換,將“最大值”慢慢“冒泡”到最后。
function bubbleSort(arr) {
const n = arr.length;
for (let i = 0; i < n - 1; i++) {
let swapped = false;
for (let j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
swapped = true;
}
}
if (!swapped) break;
}
return arr;
}
冒泡排序的行為類似一個雙重循環,外循環決定內循環的次數,內循環用于找到最大的數并將其放到外面。
適用場景:數據量小、初學者入門
時間復雜度:最好 O(n),最壞 O(n2)空間復雜度:O(1)是否穩定:是
2. 選擇排序(Selection Sort)
原理:每次選擇剩余數組中最小的元素,放到已排序部分的末尾。
function selectionSort(arr) {
const n = arr.length;
for (let i = 0; i < n; i++) {
let minIndex = i;
for (let j = i + 1; j < n; j++) {
if (arr[j] < arr[minIndex]) minIndex = j;
}
[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
}
return arr;
}
選擇排序的行為與冒泡排序相反,它每一次遍歷都是找到最小的數放在前面。
適用場景:小規模排序,不追求穩定
時間復雜度:O(n2)空間復雜度:O(1)是否穩定: 否
3. 插入排序(Insertion Sort)
原理:每次將一個元素插入到前面有序序列的正確位置。
function insertionSort(arr) {
for (let i = 1; i < arr.length; i++) {
let current = arr[i], j = i - 1;
while (j >= 0 && arr[j] > current) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = current;
}
return arr;
}
插入排序類似選擇排序,也是將數組劃分為兩個區域,左邊的第一數為有序區域,右邊為無序區域,不同的是,插入排序的每次循環不是找最小數,而是直接將無序區的第一個數取出來,插入到有序區域適當位置上。
適用場景:小規模、基本有序數據
時間復雜度:最好 O(n),最壞 O(n2)空間復雜度:O(1)是否穩定:是
4. 希爾排序(Shell Sort)
原理:分組插入排序,逐漸縮小步長(gap)來優化插入排序效率。
function shellSort(arr) {
let gap = Math.floor(arr.length / 2);
while (gap > 0) {
for (let i = gap; i < arr.length; i++) {
let temp = arr[i], j = i;
while (j >= gap && arr[j - gap] > temp) {
arr[j] = arr[j - gap];
j -= gap;
}
arr[j] = temp;
}
gap = Math.floor(gap / 2);
}
return arr;
}
希爾排序是希爾(Donald Shell)1959年提出的一種排序算法,是插入排序的改進版,也稱縮小增量排序。它是第一批沖破O(n2)的算法之一。
適用場景:中小型數組排序
時間復雜度:平均 O(n^1.3)空間復雜度:O(1)是否穩定:否
5. 歸并排序(Merge Sort)
原理:遞歸分組、排序再合并,有較強的穩定性。
function mergeSort(arr) {
if (arr.length <= 1) return arr;
const mid = Math.floor(arr.length / 2);
const left = mergeSort(arr.slice(0, mid));
const right = mergeSort(arr.slice(mid));
return merge(left, right);
}
function merge(left, right) {
const result = [];
while (left.length && right.length) {
result.push(left[0] < right[0] ? left.shift() : right.shift());
}
return result.concat(left, right);
}
適用場景:大型數據、追求穩定性
時間復雜度:O(n log n)空間復雜度:O(n)是否穩定:是
6. 堆排序(Heap Sort)
原理:構建最大堆,每次取出堆頂(最大元素)放末尾。
function heapSort(arr) {
const n = arr.length;
function heapify(i, heapSize) {
let largest = i, left = 2 * i + 1, right = 2 * i + 2;
if (left < heapSize && arr[left] > arr[largest]) largest = left;
if (right < heapSize && arr[right] > arr[largest]) largest = right;
if (largest !== i) {
[arr[i], arr[largest]] = [arr[largest], arr[i]];
heapify(largest, heapSize);
}
}
for (let i = Math.floor(n / 2) - 1; i >= 0; i--) heapify(i, n);
for (let i = n - 1; i > 0; i--) {
[arr[0], arr[i]] = [arr[i], arr[0]];
heapify(0, i);
}
return arr;
}
堆排序是選擇排序的一種,它是不穩定排序的一種。
適用場景:高效排序、內存控制需求
時間復雜度:O(n log n)空間復雜度:O(1)是否穩定:否
7. 快速排序(Quick Sort)
原理:選一個基準,將小于它的放左邊,大于它的放右邊,遞歸處理。
function quickSort(arr) {
if (arr.length <= 1) return arr;
const pivot = arr[0];
const left = [], right = [];
for (let i = 1; i < arr.length; i++) {
(arr[i] < pivot ? left : right).push(arr[i]);
}
return [...quickSort(left), pivot, ...quickSort(right)];
}
快速排序的對冒泡排序的一種改進,一個基于分治法的排序。
適用場景:追求極致性能的排序任務
時間復雜度:平均 O(n log n),最壞 O(n2)空間復雜度:O(log n)是否穩定:否
8. 計數排序(Counting Sort)
原理:統計每個值出現的次數,重建數組。
function countingSort(arr, maxVal) {
const count = new Array(maxVal + 1).fill(0);
for (let num of arr) count[num]++;
const result = [];
for (let i = 0; i <= maxVal; i++) {
while (count[i]-- > 0) result.push(i);
}
return result;
}
適用場景:數據是整數,范圍不大
時間復雜度:O(n + k)空間復雜度:O(k)是否穩定:是
9. 桶排序(Bucket Sort)
原理:將元素分散到多個“桶”中排序,最后合并。
function bucketSort(arr, bucketSize = 5) {
if (arr.length <= 1) return arr;
const min = Math.min(...arr);
const max = Math.max(...arr);
const bucketCount = Math.floor((max - min) / bucketSize) + 1;
const buckets = Array.from({ length: bucketCount }, () => []);
for (let num of arr) {
const index = Math.floor((num - min) / bucketSize);
buckets[index].push(num);
}
return buckets.flatMap(bucket => insertionSort(bucket));
}
適用場景:實數、數據分布均勻
時間復雜度:O(n + k),最壞 O(n2)空間復雜度:O(n + k)是否穩定:是
10. 基數排序(Radix Sort)
原理:按位比較,從最低位到最高位依次排序。
function radixSort(arr) {
const max = Math.max(...arr);
let exp = 1;
while (Math.floor(max / exp) > 0) {
countingByDigit(arr, exp);
exp *= 10;
}
return arr;
}
function countingByDigit(arr, exp) {
const output = new Array(arr.length).fill(0);
const count = new Array(10).fill(0);
for (let num of arr) count[Math.floor(num / exp) % 10]++;
for (let i = 1; i < 10; i++) count[i] += count[i - 1];
for (let i = arr.length - 1; i >= 0; i--) {
const digit = Math.floor(arr[i] / exp) % 10;
output[--count[digit]] = arr[i];
}
for (let i = 0; i < arr.length; i++) arr[i] = output[i];
}
適用場景:非負整數排序
時間復雜度:O(nk)空間復雜度:O(n + k)是否穩定:是
排序算法性能對比表
排序算法 | 最好 | 最壞 | 平均 | 空間復雜度 | 穩定性 |
冒泡排序 | O(n) | O(n2) | O(n2) | O(1) | ? |
選擇排序 | O(n2) | O(n2) | O(n2) | O(1) | ? |
插入排序 | O(n) | O(n2) | O(n2) | O(1) | ? |
希爾排序 | O(n log n) | O(n2) | O(n^1.3) | O(1) | ? |
歸并排序 | O(n log n) | O(n log n) | O(n log n) | O(n) | ? |
堆排序 | O(n log n) | O(n log n) | O(n log n) | O(1) | ? |
快速排序 | O(n log n) | O(n2) | O(n log n) | O(log n) | ? |
計數排序 | O(n + k) | O(n + k) | O(n + k) | O(k) | ? |
桶排序 | O(n + k) | O(n2) | O(n + k) | O(n + k) | ? |
基數排序 | O(nk) | O(nk) | O(nk) | O(n + k) | ? |
寫在最后
排序算法看似“老生常談”,但在每一場真正的算法挑戰中,它們依然是最基礎的“武器”。掌握排序,不只是為了寫出“排序函數”,更是提升你對數據結構與算法思想的理解。