10种排序算法javaScript实现
时间复杂度和空间复杂度
- 时间复杂度:执行当前算法消耗的时间
- 空间复杂度:执行当前算法需要占用多少内存
常见的几种复杂度
- O(1):常数阶
- O(n):线性阶
- O(n²):平方阶
- O(logn):对数阶
- O(nlogn):线性对数阶
排序算法分类
- 冒泡排序
- 选择排序
- 普通选择排序
- 堆排序
- 插入排序
- 普通插入排序
- 希尔排序
- 快速排序
- 归并排序
- 计数排序
- 基数排序
- 桶排序
各个排序算法的复杂度
排序名字 | 复杂度和稳定性 | 平均时间复杂度最好 | 最坏 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | O(n^2) | O(n) | O(n^2) | O(1) | 稳定 |
选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不稳定 |
堆排序 | O(n logn) | O(n logn) | O(n logn) | O(1) | 不稳定 |
插入排序 | O(n^2) | O(n) | O(n^2) | O(1) | 稳定 |
希尔排序 | O(n logn) | O(n log^2 n) | O(n log^2 n) | O(1) | 不稳定 |
快速排序 | O(n logn) | O(n logn) | O(n^2) | O(logn) | 不稳定 |
归并排序 | O(n logn) | O(n logn) | O(n logn) | O(n) | 稳定 |
计数排序 | O(n+k) | O(n+k) | O(n+k) | O(k) | 稳定 |
桶排序 | O(n+k) | O(n+k) | O(n^2) | O(n+k) | 稳定 |
基数排序 | O(n*k) | O(n*k) | O(n*k) | O(n+k) | 稳定 |
冒泡排序
核心是相邻比较,每次外层循环结束,后半部分的数组都是有序的
// 普通实现
function bubbleSort(arr) {
let sort = arr;
let leng = sort.length;
for (let i = 0; i < leng; i ++) {
for(let j = 0; j < leng - i - 1; j ++) {
if (sort[j] > sort[j+1]) swap(sort, j, j+1)
}
}
return sort
}
// 优化
function bubbleSort(arr) {
let sort = arr
let leng = sort.length
while ( leng > 0 ) {
let hasChange = false
let position = 0
// 内层循环不用每次只减一位,只需到上次发生交换的位置就可
for(let j = 0; j < leng; j ++) {
if (sort[j] > sort[j+1]) {
swap(sort, j, j+1)
position = j
hasChange = true
}
}
if (!hasChange) break // 如果整个循环都没有发生交换,证明已经有序了
leng = position
}
return sort
}
选择排序
外层循环每次结束,对应数组之前的元素就是有序的,有点像反着的冒泡排序,但原理是不一样的
function selectSort(arr) {
let sort = arr
let leng = sort.length
for (let i = 0; i < leng; i ++) {
let index = i
for(let j = i+1; j < leng; j ++) {
if (sort[j] < sort[index]) {
index = j
}
}
if (i !== index) swap(sort, i, index)
}
return sort
}
堆排序
// 堆排序
function heapSort(arr) {
var arr_length = arr.length
if (arr_length <= 1) return arr
// 1. 建最大堆
// 遍历一半元素就够了
// 必须从中点开始向左遍历,这样才能保证把最大的元素移动到根节点
for (var middle = Math.floor(arr_length / 2); middle >= 0; middle--) maxHeapify(arr, middle, arr_length)
// 2. 排序,遍历所有元素
for (var j = arr_length; j >= 1; j--) {
// 2.1. 把最大的根元素与最后一个元素交换
swap(arr, 0, j - 1)
// 2.2. 剩余的元素继续建最大堆
maxHeapify(arr, 0, j - 2)
}
return arr
}
// 建最大堆
function maxHeapify(arr, middle_index, length) {
// 1. 假设父节点位置的值最大
var largest_index = middle_index
// 2. 计算左右节点位置
var left_index = 2 * middle_index + 1,
right_index = 2 * middle_index + 2
// 3. 判断父节点是否最大
// 如果没有超出数组长度,并且子节点比父节点大,那么修改最大节点的索引
// 左边更大
if (left_index <= length && arr[left_index] > arr[largest_index]) largest_index = left_index
// 右边更大
if (right_index <= length && arr[right_index] > arr[largest_index]) largest_index = right_index
// 4. 如果 largest_index 发生了更新,那么交换父子位置,递归计算
if (largest_index !== middle_index) {
swap(arr, middle_index, largest_index)
// 因为这时一个较大的元素提到了前面,一个较小的元素移到了后面
// 小元素的新位置之后可能还有比它更大的,需要递归
maxHeapify(arr, largest_index, length)
}
}
插入排序
// 插入排序
function heapSort(arr) {
let sort = arr
let leng = sort.leng
for (let i = 1; i < leng; i ++) {
let current = sort[i]
let last = i - 1
// 前面的元素更大,并且还没遍历完
while (arr[last] >= current && last >= 0) {
// 使用前面的值覆盖当前的值
arr[last + 1] = arr[last]
// 向前移动一个位置
last--
}
sort[ordered]
}
return arr
}
希尔排序
// 希尔排序
function shellSort(arr) {
// 外层循环逐步缩小增量 gap 的值
for (let gap = 5; gap > 0; gap = Math.floor(gap / 2)) {
// 中层和内层是插入排序
// 普通插入排序从第1个元素开始,这里分组了,要看每一组的第1个元素
// 共分成了 gap 组,第一组的第1个元素索引为 gap
// 第一组元素索引为 0, 0+gap, 0+2*gap,...,第二组元素索引为 1, 1+gap, 2+2*gap,...
for (let i = gap; i < arr.length; i++) {
let current_ele = arr[i]
// 普通插入排序时,j 每次减少1,即与前面的每个元素比较
// 这里 j 每次减少 gap,只会与当前元素相隔 n*(gap-1) 的元素比较,也就是只会与同组的元素比较
let ordered_index = i - gap
while (ordered_index >= 0 && arr[ordered_index] > current_ele) {
arr[ordered_index + gap] = arr[ordered_index]
ordered_index -= gap
}
arr[ordered_index + gap] = current_ele
}
}
return arr
}
快速排序
// 快速排序
function quickSort(arr) {
let sort = arr
let leng = sort.length
if (leng <= 1) return sort
let middle = [];
let left = [];
let right = [];
for (let i = 0; i < leng; i ++) {
if (arr[i] === arr[0]) middle.push(arr[i])
if (arr[i] < arr[0]) left.push(arr[i])
if (arr[i] > arr[0]) right.push(arr[i])
}
return quickSort(left).concat(middle, quickSort(right))
}
归并排序
// 分割
function mergeSort2(arr) {
// 如果只剩一个元素,分割结束
if (arr.length < 2) return arr
// 否则继续分成两部分
let middle_index = Math.floor(arr.length / 2),
left = arr.slice(0, middle_index),
right = arr.slice(middle_index)
return merge2(mergeSort2(left), mergeSort2(right))
}
// 合并
function merge2(left, right) {
let result = []
// 当左右两个数组都还没有取完的时候,比较大小然后合并
while (left.length && right.length) {
if (left[0] < right[0]) result.push(left.shift())
else result.push(right.shift())
}
// 其中一个数组空了,另一个还剩下一些元素
// 因为是已经排序过的,所以直接concat就好了
// 注意 concat 不改变原数组
if (left.length) result = result.concat(left)
if (right.length) result = result.concat(right)
return result
}
计数排序
function countingSort(array) {
let count_arr = [], result_arr = []
// 统计出现次数
for (let i = 0; i < array.length; i++) {
count_arr[array[i]] = count_arr[array[i]] ? count_arr[array[i]] + 1 : 1
}
// 遍历统计数组,放入结果数组
for (let i = 0; i < count_arr.length; i++) {
while (count_arr[i] > 0) {
result_arr.push(i)
count_arr[i]--
}
}
return result_arr
}
参考:https://juejin.cn/post/6844903814340804615