桶排序
- 性能:浪费空间,且只能比较自然数,时间复杂度是 O(m+n)
- 需要对数据范围在
0~n之间的整数进行排序
var testArr = [3, 5, 3, 5, 9, 7, 6]
console.log('原数组:', arr)
function skipSort (n, skipArr) {
let arr = []
let sortArr = []
arr.length = n
for (let i = 0; i <= n; i++) {
arr[i] = 0
}
for (let j = 0; j < skipArr.length; j++) {
arr[skipArr[j]]++
}
for (let t = 0; t <= n; t++) {
if (arr[t]) {
for (let k = 1; k <= arr[t]; k++) {
sortArr.push(t)
}
}
}
return sortArr
}
console.log('桶排序:', skipSort(10, testArr))
冒泡排序
- 性能:时间复杂度是O(n^2),执行效率低
- 元素项向上移动至正确的顺序,就好像气泡往上冒一样
- 从小到大排序,比较两个相邻的项,如果第一个大于第二个则交换他们的位置
var arr = [3, 5, 3, 5, 9, 7, 6]
console.log('原数组:', arr)
function bubbleSort (arr) {
let len = arr.length
for (let i = 0; i < len - 1; i++) {
for (let j = 0; j < len - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
let temp = arr[j]
arr[j] = arr[j + 1]
arr[j + 1] = temp
}
}
}
return arr
}
console.log('冒泡排序:',bubbleSort(arr))
快速排序
chrome使用快速排序的一个变体作为Array.prototype.sort的实现- 性能:时间复杂度最差是O(n^2),平均时间复杂度为O(NlogN)
- 首先,在数组中选择一个中间项作为主元
- 创建两个指针,左边的指向数组第一个项,右边的指向最后一个项。移动右指针,直到找到一个比主元小的项,接着,移动左边的指针,直到找到一个比主元大的项,然后交换它们。重复这个过程,直到左侧的指针超过了右侧的指针。这个使比主元小的都在左侧,比主元大的都在右侧。这一步叫
划分操作。 - 接着,算法对划分后的小数组(较主元小的值组成的的小数组, 以及较主元大的值组成的小数组)重复之前的两个步骤,直到排序完成
切记:一定要先从右往左找,再从左往右找,否则会出错
var arr = [3, 5, 3, 5, 9, 7, 6]
console.log('原数组:', arr)
function quickSort (arr, left, right) {
let len = arr.length
left = typeof left === 'undefined' ? 0 : left
right = typeof right === 'undefined' ? len - 1 : right
if (left > right) {
return
}
let temp = arr[left]
let i = left
let j = right
let t
while (i !== j) {
while (arr[j] >= temp && i < j) {
j--
}
while (arr[i] <= temp && i < j) {
i++
}
if (i < j) {
t = arr[i]
arr[i] = arr[j]
arr[j] = t
}
}
arr[left] = arr[i]
arr[i] = temp
quickSort(arr, left, i - 1)
quickSort(arr, i + 1, right)
return arr
}
console.log('快速排序:',quickSort(arr))
选择排序
- 大概思路是找到最小的放在第一位,找到第二小的放在第二位,以此类推 算法复杂度O(n^2)
var arr = [3, 5, 3, 5, 9, 7, 6]
console.log('原数组:', arr)
function selectSort (arr) {
let len = arr.length
let minIndex
let temp
for (let i = 0; i < len; i++) {
minIndex = i
temp = arr[i]
for (let j = i + 1; j < len; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j
}
}
arr[i] = arr[minIndex]
arr[minIndex] = temp
}
return arr
}
console.log('选择排序:',selectSort(arr))
插入排序
- 每次排一个数组项,假设数组的第一项已经排序
- 接着,把第二项与第一项进行对比,第二项是该插入到第一项之前还是之后
- 第三项是该插入到第一项之前还是第一项之后还是第三项
var arr = [3, 5, 3, 5, 9, 7, 6]
console.log('原数组:', arr)
function insertSort (arr) {
let len = arr.length
let preIndex
for (let i = 1; i < len; i++) {
preIndex = i - 1
current = arr[i]
while (preIndex >= 0 && arr[preIndex] > current) {
arr[preIndex + 1] = arr[preIndex]
preIndex--
}
arr[preIndex + 1] = current
}
return arr
}
console.log('插入排序:', insertSort(arr))
归并排序
Mozilla Firefox使用归并排序作为Array.prototype.sort的实现归并排序是一种分治算法。本质上就是把一个原始数组切分成较小的数组,直到每个小数组只有一个位置,接着把小数组归并成较大的数组,在归并过程中也会完成排序,直到最后只有一个排序完毕的大数组分治算法的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解,就可得到原问题的解。
var arr = [3, 5, 3, 5, 9, 7, 6]
console.log('原数组:', arr)
function mergeSort (arr) {
let len = arr.length
if (len < 2) {
return arr
}
let middle = Math.floor(len / 2)
let left = arr.slice(0, middle)
let right = arr.slice(middle)
return merge(mergeSort(left), mergeSort(right))
}
function merge (left, right) {
let result = []
while (left.length && right.length) {
if (left[0] < right[0]) {
result.push(left.shift())
} else {
result.push(right.shift())
}
}
result.push(...left)
result.push(...right)
return result
}
console.log('归并排序:', mergeSort(arr))
堆排序
-
堆排序实际上是利用
堆的性质来进行排序的,我们通常说的堆就是二叉堆,二叉堆又称完全二叉树或者近似完全二叉树。堆排序是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。数组可以根据索引直接获取元素,时间复杂度为O(1) -
最大堆的特性如下:
- 父结点的键值总是大于或者等于任何一个子节点的键值
- 每个结点的左子树和右子树都是一个最大堆
-
最小堆的特性如下:
- 父结点的键值总是小于或者等于任何一个子节点的键值
- 每个结点的左子树和右子树都是一个最小堆
-
算法思想
将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。
- 构建初始堆。一般升序采用大顶堆,降序采用小顶堆。从第一个非叶子结点从下至上,从右至左调整结构;
- 交换堆顶元素和末尾元素,使最大值沉到数组末尾,重新调整堆结构,使其满足定义;
- 然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换,直到整个序列有序。
const arr = [21, 34, 56, 2, 4, 9, 87]
console.log('原数组:', arr)
let len = arr.length
// 构建最大堆
function buildMaxHeap (arr) {
let mid = Math.floor(len / 2)
for (let i = mid; i >= 0; i--) {
heapify(arr, i)
}
}
// 子数的调整
function heapify (arr, i) {
let maxIndex = i
let left = 2 * i + 1
let right = 2 * i + 2
if (left <len && arr[maxIndex] < arr[left]) {
maxIndex = left
}
if (right <len && arr[maxIndex] < arr[right]) {
maxIndex = right
}
if (maxIndex !== i) {
swap(arr, i, maxIndex)
heapify(arr, maxIndex)
}
}
// 交换两值
function swap (arr, i, j) {
[arr[i], arr[j]] = [arr[j], arr[i]]
}
// 堆排序
function heapSort (arr) {
buildMaxHeap(arr)
for (let i = arr.length - 1; i>=0; i--) {
swap(arr, 0, i)
len--
heapify(arr, 0)
}
return arr
}
console.log('堆排序:', heapSort(arr))
各个排序算法的性能比较
