算法名稱 | 平均時間複雜度 | 最好時間複雜度 | 最差時間複雜度 | 空間複雜度 | 穩定性 |
冒泡排序 | O(n2) | O(n) | O(n2) | O(1) | 穩定 |
選擇排序 | O(n2) | O(n2) | O(n2) | O(1) | 不穩定 |
插入排序 | O(n2) | O(n) | O(n2) | O(1) | 穩定 |
希爾排序 | O(n1.3) (不肯定) | O(n) | O(n2) | O(1) | 不穩定 |
快速排序 | O(nlog2n) | O(nlog2n) | O(n2) | O(log2n) 至O(n) | 不穩定 |
歸併排序 | O(nlog2n) | O(nlog2n) | O(nlog2n) | O(n) | 穩定 |
1、冒泡排序算法
冒泡排序的思路是:假設正在將一組數字按照升序排列,從第0個元素到第n-1個元素遍歷,若前面一個元素大於後面一個元素,則交換兩個元素,這樣可將整個序列中最大的元素冒泡到最後,而後再從第0個到第n-2遍歷,如此往復,直到完成。shell
一、初級版:冒泡排序不管如何都要執行完兩重循環,故最好、最壞和平均時間複雜度均爲O(n2),不須要額外空間。冒泡排序是穩定的。express
function bubbleSort(array) { const n = array.length - 1 for (let i = 0; i < n; i++) { for (let j = 0; j < n - i; j++) { if (array[j] > array[j + 1]) { const temp = array[j] array[j] = array[j + 1] array[j + 1] = temp } } }
return array }
二、改進版:在內層循環以前設置一個標誌性變量pos,用於記錄每趟排序中最後一次進行交換的位置,因爲pos位置以後的記錄均已交換到位,故在進行下一趟排序是隻要掃描到pos位置。冒泡排序的最好時間複雜度能夠提高到O(n)。(並不意味着實際應用中改進版比初級版快,只是最好狀況下快)數組
function bubbleSort(array) { let i = array.length - 1; while (i > 0) { let pos = 0; for (let j = 0; j < i; j++) { if (array[j] > array[j + 1]) { pos = j; let temp = array[j]; array[j] = array[j + 1]; array[j + 1] = temp; } } i = pos; } return array; }
2、選擇排序數據結構
選擇排序的思路是:從所有序列中選取最小的,與第0個元素交換,而後從下一個元素日後找出最小的,與該元素元素交換,以此類推,直到選取最後一個元素。由於不管如何都要完整地執行內外兩重循環,故最好、最差和平均時間複雜度都是O(n2),惟一的好處就是不佔用額外的內存空間。選擇排序是不穩定的。less
function selectionSort(array) { let length = array.length; for (let i = 0; i < length - 1; i++) { let min = i; for (let j = i + 1; j < length; j++) { if (array[j] < array[min]) { min = j; } } let temp = array[i]; array[i] = array[min]; array[min] = temp; } return array; }
3、簡單插入排序ui
簡單插入排序思路是相似撲克牌的排序,每次從未排序序列的第一個元素,插入到已排序序列中的合適位置。編碼
一、初級版:經過兩重循環,最差和平均時間複雜度爲O(n2),最好狀況是原序列已有序,則忽略內層循環,時間複雜度O(n)。插入排序是穩定的。spa
function insertionSort(array) { for (let i = 1; i < array.length; i++) { let key = array[i]; let j = i - 1; while (j >= 0 && array[j] > key) { array[j + 1] = array[j]; j--; } array[j + 1] = key; } return array; }
二、改進版:二分查找改進的插入排序
code
function insertionSort(array) { for (let i = 1; i < array.length; i++) { let key = array[i], left = 0, right = i - 1; while (left < right) { let middle = Math.floor((left + right) / 2); if (key < array[middle]) { right = middle - 1; } else { left = middle; } } for (let j = i - 1; j >= left; j--) { array[j + 1] = array[j]; } array[left] = key; } return array; }
4、希爾排序
希爾排序的思路是,先以一個較大的增量,將序列分紅幾個子序列,將這幾個子序列分別排序後,合併,在縮小增量進行一樣的操做,直到增量爲1時,序列已經基本有序,再進行簡單插入排序的效率就會較高。
希爾排序的核心在於間隔序列的設定。既能夠提早設定好間隔序列,也能夠動態定義間隔序列。
一、動態間隔序列希爾排序(能夠設置間隔區間)
function shellSort(array) { const N = array.length let gap = 1; while (gap < N / 3) { gap = gap * 3 + 1; } while (gap >= 1) { for (let i = gap; i < N; i++) { for (let j = i; j >= gap && array[j] > array[j - gap]; j -= gap) { let tmp = array[j] array[j] = array[j - gap] array[j - gap] = tmp } } gap = (gap - 1) / 3 } return array }
二、硬編碼間隔序列的希爾排序(gaps 是 [10,4,1] 這樣設定好間隔的數組)
function shellSort1(array, gaps) { for (let g = 0; g < gaps.length; g++) { const gap = gaps[g] for (let i = gap; i < array.length; i++) { for (j = i; j >= gap && array[j] > array[j - gap]; j -= gap) { let tmp = array[j] array[j] = array[j - gap] array[j - gap] = tmp } } } }
5、快速排序
快速排序的思想是,選取第一個數爲基準,經過一次遍歷將小於它的元素放到它的左側,將大於它的元素放到它的右側,而後對它的左右兩個子序列分別遞歸地執行一樣的操做。
function quickSort (array) { if (array.length <= 1) { return [] } const lesser = []; const greater = []; const pivot = array[0]; for (let i = 1; i < array.length; i++) { if (array[i] < pivot) { lesser.push(array[i]); } else { greater.push(array[i]); } } return quickSort(lesser).concat(pivot, quickSort(greater)); }
6、歸併排序
歸併排序的思路是,利用二分的特性,將序列分紅兩個子序列進行排序,將排序後的兩個子序列歸併(合併),當序列的長度爲2時,它的兩個子序列長度爲1,即視爲有序,可直接合並,即達到歸併排序的最小子狀態。
一、初級版(效率低)
function mergeSort(array) { if (array.length < 2) { return array; } const middle = parseInt(array.length / 2); const left = array.slice(0, middle); const right = array.slice(middle); return merge(mergeSort(left), mergeSort(right)); } function merge(left, right) { const newArray = []; while (left.length && right.length) { if (left[0] <= right[0]) { newArray.push(left.shift()); } else { newArray.push(right.shift()); } } while (left.length) { newArray.push(left.shift()); } while (right.length) { newArray.push(right.shift()); } return newArray; }
二、改進版(效率較高)
function mergeSort(array = [], start = 0, length) { length = length || array.length; if (length - start < 2) { return; } const mid = Math.floor((start + length) / 2); mergeSort(array, start, mid); mergeSort(array, mid, length); merge(array, start, mid, length); return array; } function merge(array, start, mid, length) { const left = array.slice(start, mid); const right = array.slice(mid, length); left.push(Number.MAX_SAFE_INTEGER); right.push(Number.MAX_SAFE_INTEGER); for (let i = start, j = 0, k = 0; i < length; i++) { if (left[j] < right[k]) { array[i] = left[j]; j++; } else { array[i] = right[k]; k++; } } }
三、高級版(效率高)
function mergeSort2(array = []) {
if (array.length < 2) { return; } let step = 1; let left; while (step < array.length) { left = 0;while (left + step * 2 <= array.length) { merge2(array, left, left + step, left + step * 2); left = left + step * 2; } if (left + step < array.length) { merge2(array, left, left + step, array.length); } step *= 5; } return array } function merge2(array, start, mid, length) { const rightArr = new Array(length - mid + 1); const leftArr = new Array(mid - start + 1); let k = mid; for (let i = 0; i < length - mid; i++) { rightArr[i] = array[k]; k++; } k = start; for (let i = 0; i < mid - start; i++) { leftArr[i] = array[k]; k++; } rightArr[length - mid] = Infinity; leftArr[mid - start] = Infinity; let m = 0; let n = 0; for (k = startLeft; k < stopRight; ++k) { if (leftArr[m] <= rightArr[n]) { array[k] = leftArr[m]; m++; } else { array[k] = rightArr[n]; n++; } } }
歸併高級版比改進版效率高的緣由有兩個:
一、算法遞歸次數少;
二、新建Array,對其賦值效率比Array.slice()高
對比1000萬的數組排序所用的時間