「排序法」的解題技巧:泡泡排序等經典排序法
在這篇文章中,我介紹了幾種基本的排序方法,包括選擇排序、泡沫排序、插入排序、合併排序以及快速排序。我闡述了每種排序法的時間複雜度和空間複雜度,並通過具體的 JavaScript 實例和動畫來展示每個排序算法的具體運作過程。這些排序方法涵蓋了從基本到較為進階的技術,幫助讀者理解各種排序技術在實際應用中的表現及效率。
本文章會介紹些經典的排序法,比較進階如堆積排序(Heap Sort) 、希爾排序(Shell Sort)等則可先見參考資料。
每種排序法會列出時間複雜度和空間複雜度,此處說明可見 時間/空間複雜度的簡介與實例 一文。
- 選擇排序法(Selection Sort)
- 找到最小值,移到最左邊
- 泡沫排序法(Bubble Sort)
- 跟隔壁互相比較順序錯了就交換,順序對了就接棒繼續到最後排好一個,再從頭開始
- 插入排序法(Insertion Sort)
- 一個一個照順序插入到順序對的位置,最後一次插到對的地方剛好整個排好
- 合併排序法(Merge Sort)
- 重複切一半到最小,重複排好左邊排好右邊再合併
- 快速排序法(Quick Sort)
- 隨機找一數調整到左邊元素都比它小右邊元素都比他大,左右再重複操作
一、動畫展示各排序法邏輯
1. 選擇排序法/圖片來源: SelectionSort
2. 泡沫排序法/圖片來源: wiki/BubbleSort
3. 插入排序法/圖片來源: wiki/InsertSort
4. 合併排序法/圖片來源: wiki/MergeSort
5. 快速排序法/圖片來源: wiki/Quicksort
二、選擇排序法
- 時間複雜度: O(n^2)
- 空間複雜度: O(1)
const selectionSort = (arr) => {
const length = arr.length;
for (let i = 0; i < length; i++) {
let min = arr[i];
let minIndex = i;
for (let j = i; j < length; j++) {
if (arr[j] < min) {
min = arr[j];
minIndex = j;
}
}
// ES6 的用法,交換兩個數值
[arr[minIndex], arr[i]] = [arr[i], arr[minIndex]];
}
return arr;
}
let arr = [1, 3, 5, 7, 2, 4, 8 ,9]
selectionSort(arr) // [1, 2, 3, 4, 5, 7, 8, 9]
三、泡沫排序法
稱為「泡沫」排序法,因為元素很像「浮」了上來
- 時間複雜度
- 平均時間複雜度: O(n^2)
- 最壞時間複雜度: O(n^2)
- 最優時間複雜度: O(n)
- 空間複雜度: O(1)
加上 swapped,如果輸入是已經排好的陣列就只會跑一次內圈,然後就跳掉了,單純跑完一輪不改動,所以時間複雜度會是O(n)
。
const bubbleSort = (arr) => {
const n = arr.length;
let swapped = true;
for (let i = 0; i < n && swapped; i++) {
swapped = false;
for (let j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
swapped = true;
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
}
return arr;
}
let arr = [1, 3, 5, 7, 2, 4, 8 ,9]
bubbleSort(arr) // [1, 2, 3, 4, 5, 7, 8, 9]
四、插入排序法
- 時間複雜度
- 平均時間複雜度: O(n^2)
- 最壞時間複雜度: O(n^2)
- 最優時間複雜度: O(n)
- 空間複雜度: O(1)
已經排好的陣列,裡面的while
只要跑一次就會結束了,就時間複雜度會是O(n)
。
const insertionSort = (arr) => {
const n = arr.length;
for (let i = 1; i < n; i++) {
let position = i;
const target = arr[i];
while (i >= 0 && arr[position - 1] > target) {
[arr[position], arr[position - 1]] = [arr[position - 1], arr[position]];
position--;
}
}
return arr;
}
let arr = [1, 3, 5, 7, 2, 4, 8 ,9]
insertionSort(arr) // [1, 2, 3, 4, 5, 7, 8, 9]
五、合併排序法
- 時間複雜度: O(nlogn)
- 空間複雜度: O(n)
合併排序法要用到遞迴的概念挺為複雜,但在時間複雜度上優於其他排序方式,此處先記錄最簡單能理解的解法,請搭配動畫理解。
const mergeSort = (arr) => {
// 合併
const sortBeforeMerge = (arr1, arr2) => {
let sortedArr = []
while (arr1.length && arr2.length) {
let minElement = (arr1[0] < arr2[0]) ? arr1.shift() : arr2.shift()
sortedArr.push(minElement)
}
sortedArr = arr1.length ? sortedArr.concat(arr1) : sortedArr.concat(arr2)
return sortedArr
}
// 分割
if (arr.length <= 1) {
return arr
}
let middleIndex = Math.floor(arr.length / 2)
let firstHalf = arr.slice(0, middleIndex)
let secondHalf = arr.slice(middleIndex)
return sortBeforeMerge(mergeSort(firstHalf), mergeSort(secondHalf))
}
let arr = [1, 3, 5, 7, 2, 4, 8 ,9]
mergeSort(arr) // [1, 2, 3, 4, 5, 7, 8, 9]
六、快速排序法
- 時間複雜度
- 平均時間複雜度: O(nlogn)
- 最壞時間複雜度: O(n2)
- 最優時間複雜度: O(nlogn)
- 空間複雜度為:根據實現的方式不同而不同
此處先記錄最簡單能理解的解法,請搭配動畫理解。
function quickSort(arr) {
if (arr.length <= 1 ) {
return arr
}
let pivot = arr[arr.length -1]
let left = []
let right = []
for(let i = 0; i < arr.length - 1; i++) {
if (arr[i] < pivot) {
left.push(arr[i])
} else {
right.push(arr[i])
}
}
return [...quickSort(left), pivot, ...quickSort(right)]
}
let arr = [1, 3, 5, 7, 2, 4, 8 ,9]
quickSort(arr) // [1, 2, 3, 4, 5, 7, 8, 9]
資料來源:一起用 JavaScript 來複習經典排序法吧!演算法 合併排序法(Merge Sort)竹白記事本/排序演算法排序之快速排序法 (Quick Sort)