<漫画算法>常见排序算法整理
jiangtao opened this issue · comments
整理一下之前看《漫画算法》上的一些基础排序算法。部分资料引用wiki自己做过回顾,已经掌握好排序的童鞋,可以忽略。
排序算法
排序算法常见要求:
- 时间复杂度 (最好和最坏情况,什么时候最好,什么时候最坏)
- 空间复杂度 (最好和最坏情况,什么时候最好,什么时候最坏)
- 稳定性 (元素相等的情况下, 排序之后位置是否发生变化)
快速排序 o(n*logn)
- 使用条件 普通的数组
- 交换排序 (不稳定排序)
具体实现
- 挑选基准值:从数列中挑出一个元素,称为“基准”(pivot),
- 分割:重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(与基准值相等的数可以到任何一边)。在这个分割结束之后,对基准值的排序就已经完成,
- 递归排序子序列:递归地将小于基准值元素的子序列和大于基准值元素的子序列排序。
额外空间实现
这种空间复杂度高,代码简洁。
function quickSort2(A) {
if (A.length < 2) return A
let pivotIndex = A.length >> 1
let pivotValue = A.splice(pivotIndex, 1)[0]
let left = [], right = []
for(let i = 0; i < A.length; i++) {
(A[i] > pivotValue ? right : left).push(A[i])
}
return quickSort2(left).concat(pivotValue, quickSort2(right))
}
原地排序实现
function swap (A, i, j ) {
let t = A[j]
A[j] = A[i]
A[i] = t
}
/**
* 双边循环来做, 好理解
* 双指针 left与小的比较找到最前面的大的, right找大的比较,找到最后面的小的, 然后交换
* 小的就在前面, 大的就在后面咯
* @param {*} A
* @param {*} left
* @param {*} right
*/
function partition (A, left, right) {
let pivotIndex = left
let pivot = A[left]
while(left !== right) {
while (left < right && A[right] > pivot) {
right--
}
while(left < right && A[left] <= pivot) {
left++
}
if (left < right) {
swap(A, left, right)
}
}
swap(A, pivotIndex, left)
return left
}
/**
* 1. 挑选基准值:从数列中挑出一个元素,称为“基准”(pivot),
* 2. 分割:重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(与基准值相等的数可以到任何一边)。在这个分割结束之后,对基准值的排序就已经完成,
* 3. 递归排序子序列:递归地将小于基准值元素的子序列和大于基准值元素的子序列排序。
**/
function quickSort (A, left = 0, right = A.length - 1) {
if (left < right) {
const pivot = partition (A, left, right)
quickSort(A, left, pivot - 1)
quickSort(A, pivot + 1, right)
}
return A
}
冒泡排序 o(n*n)
- 使用条件 普通的数组
- 交换排序 (稳定排序)
i∈[0,N-1) //循环N-1遍
j∈[0,N-1-i) //每遍循环要处理的无序部分
swap(j,j+1) //两两排序(升序/降序)
分为有序区和无序区, 想象就跟水泡一下,沉的在下面 (有序区), 轻的在上面 (无序区)..
- 有序区不用做比较
- 有序区放在末端,当判定已经拍完序之后就不用再比较了。
function swap(A, i, j) {
let t = A[i]
A[i] = A[j]
A[j] = t
}
function bubbleSort(A) {
const len = A.length
for(let i = 0; i < len; i++) {
// 假定无序区已排序完, 若本次无序区已排序完,则代表排序已经完成,则退出比较,防止多次交换检测
let sorted = true
for(let j = 0; j < len - i - 1;j++) {
if (A[j] > A[j+1]) {
swap(A, j, j + 1)
sorted = false
}
}
if (sorted) break
}
return A
}
计数排序 o(n+k)
计数排序主要利用了数组索引自带排序特性
- 使用条件 值最大最小值确定, 且范围差不大的情况下(MaxValue - minValue + 1 <= 100)
注: 最大值和最小值差太大, 会造成创建的数组过大, 内存过大;
当数组值不是整数 的时候 不适合用这种方法
- 稳定排序
代码实现
// 计数排序适合分布比较均匀的排序
function coutSort(A) {
const max = Math.max.apply(null, A)
const min = Math.min.apply(null, A)
const len = max - min + 1
const L = new Array(len)
let R = []
// 统计数字出现的次数,避免造成不必要undefine的空间浪费, 存储 index (value - min)
for(let i = 0; i < A.length; i++) {
let v = A[i] - min
L[v] >= 1 ? L[v]++ : (L[v] = 1)
}
// 利用数组的索引自带排序特性
for(let i = 0; i < len; i++) {
while(L[i]) {
R.push(i + min)
L[i]--
}
}
return R
}
桶排序
样例数据 [1,2,4,100,50]
原理
- 桶排序是对计数排序的补充
- 桶的数量 N <= Array.length, 分成 N个桶, 分桶是根据 值所在的区间来分, 比如d = 最大值-最小值, value - 最小值 / d 就是桶的范围比 可求出对应的桶的索引值
- 在进行每个桶的排序, 可以是桶自己的排序 也可以其他排序
说明
- 桶排序弥补了 计数排序的一些缺陷, 数值必须是整数;
代码实现
function bucketSort (A, bucketLen = A.length) {
const max = Math.max.apply(null, A)
const min = Math.min.apply(null, A)
const d = max - min
const bucketList = Array.apply(null, {length: bucketLen}).map(_ => [])
for(let i = 0; i < A.length; i++) {
// 索引结果: idx = len * d / s
let idx = parseInt((A[i] - min) * (bucketLen - 1) / d)
bucketList[idx].push(A[i])
}
for(let i = 0; i < bucketLen; i++) {
bucketList[i].sort((a, b) => a - b)
}
return bucketList.reduce((l, p) => (l = l.concat(p)), [])
}
堆排序
后续补充
归并排序
后续补充