算法名称 | 平均时间复杂度 | 最好时间复杂度 | 最差时间复杂度 | 空间复杂度 | 稳定性 |
冒泡排序 | 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万的数组排序所用的时间