起因
写这篇文章的起因是看到网上有些写排序算法的代码写得比较冗长,而且不易懂,反正我有些看不懂,正好最近也在复习排序,于是coding!!
介绍
第一篇关于排序的算法都是选择时间复杂度为nlogn
的,原因是实现较为复杂,日常或者面试中出现频率也比较高,感觉比较有意义。接下来我们来做一个简单介绍。
排序方法 | 时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 |
---|---|---|---|---|
快速排序 | O(n*log(n)) | O(n^2) | O(n*log(n)) | O(log(n)) |
归并排序 | O(n*log(n)) | O(n*log(n)) | O(n*log(n)) | O(n) |
堆排序 | O(n*log(n)) | O(n*log(n)) | O(n*log(n)) | O(1) |
这只是一个大概的情况,因算法的实现不同可能空间复杂度会有差异,甚至写一个快排但其实是n^2复杂度的也不是不可能!接下来我们来具体实现吧
实现
快排
-
基本思想 : 标记一个基准(
priot
)通过递归将比基准大的数放一边,比基准小的数放一边,递归的返回值是一个局部有序的序列 -
算法描述
- 从数组中挑选一个
priot
- 循环数组 将小于
priot
的元素放在left数组 将大于priot
的元素放在right数组 - 递归
left
,right
数组 递归的条件是数组大小 >= 2 - 将
left
,right
递归返回的局部有序数组重组为更大的有序数组
- 从数组中挑选一个
-
gif图解
七行极简快排
js
const quickSort = array => {
if (array.length < 2) return array
const priot = array.pop()
const left = array.filter(num => num < priot) //left, right可以用reduce实现
const right = array.filter(num => num >= priot)
return [...quickSort(left), priot, ...quickSort(right)]
}
利用partition函数
js
function quickSort(arr) {
if (arr.length < 2) return arr
const pivotIndex = partition(arr, 0, arr.length - 1)
return [...quickSort(arr.slice(0, pivotIndex)), arr[pivotIndex], ...quickSort(arr.slice(pivotIndex + 1))]
}
function partition(arr, left, right) {
const pivot = arr[right]
let i = left - 1
for (let j = left; j < right; j++) {
if (arr[j] < pivot) {
;[arr[++i], arr[j]] = [arr[j], arr[i]]
}
}
;[arr[i + 1], arr[right]] = [arr[right], arr[i + 1]]
return i + 1
}
归并排序
- 算法思想 : 基于分治思想的排序算法,将无序数组大小不断切分,再不断数组有序化
- 算法描述
- 选择无序数组的中间索引,将数据切割为
left
,right
数组 - 递归
left
,right
数组 临界值是数组大小 >= 2 - 完成递归之后就开始合并无序数组
- 新建临时数组arr, 储存合并之后的有序数组
- 按升序或者降序的逻辑比较每个
left
,right
数组中的元素,填入临时数组中 left
,right
数组中可能还有未比较的元素,直接连接到临时数组中 原因是他们都是有序化的数组,未比较的元素一定最大(升序)或者最小(降序)的,可以无缝连接arr
- 选择无序数组的中间索引,将数据切割为
- gif图解
十八行极简归并
js
const mergeSort = array => {
let length = array.length
if (length < 2) return array
const mid = Math.floor(length / 2)
const left = array.slice(0, mid)
const right = array.slice(mid)
return merge(mergeSort(left), mergeSort(right))
}
const merge = (left, right) => {
let arr = []
let i = 0, j = 0;
while (i < left.length && j < right.length) {
if (left[i] > right[j]) arr.push(right[j++])
else arr.push(left[i++])
}
arr = arr.concat(left.slice(i), right.slice(j))
return arr
}
堆排序
- 算法思想 :类似于完全二叉树的结构 将待排序数组视为一个二叉堆,然后通过一系列的堆调整操作,使得数组满足大根堆(或小根堆)
- 算法描述
- heapify函数
- 是堆调整函数,旨在将当前节点
cur
调整到属于它的位置 - 参数
array, cur, end
分别是代调整数组,当前节点, 无序堆最末位置 - 根据大根堆(或小根堆)的逻辑更新当前节点cur是否比其左右节点大(小), 如果cur更新则交换元素 并继续向下调整
- 是堆调整函数,旨在将当前节点
- 建堆 从最后一个非叶子节点调整
- 形成有序堆 每次把堆顶和最后一个元素交换,有序序列从最后一个元素开始增加 并重新调整堆 最后整体有序
- heapify函数
- gif图解
十九行极简堆排
js
const heapSort = array => {
const length = array.length
for (let i = Math.floor(length / 2) - 1; i >= 0; i--) heapify(array, i, length)
for (let i = length - 1; i > 0; i--) {
[array[0], array[i]] = [array[i], array[0]]
heapify(array, 0, i)
}
return array
}
const heapify = (array, cur, end) => {
let max = cur
let left = cur * 2 + 1, right = cur * 2 + 2
if (left < end && array[left] > array[max]) max = left
if (right < end && array[right] > array[max]) max = right
if (max !== cur) {
[array[max], array[cur]] = [array[cur], array[max]]
heapify(array, max, end)
}
}
测试效率
虽然看起来实现了都是nlogn的时间复杂度的算法,但是不免怀疑真的比我的冒泡排序,选择排序快吗?? 实践出真知! coding!
写一个测试时间的函数
genRandomList
生成10000个随机数的数组testCostTime
计算消耗的时间isOrder
并且判断是否是有序的result.slice
切片前20个数看看效果
js
// testTime.js
const genRandomList = () => {
const array = Array.from({ length: 10000 }, (_, index) => index)
for (let i = 0; i < array.length; i++) {
let randomIndex = Math.floor(Math.random() * (i + 1))
;[array[randomIndex], array[i]] = [array[i], array[randomIndex]]
}
return array
}
const testCostTime = (array, sort, name) => {
const start = performance.now()
let result = sort(array)
let isOrder = result?.every((value, index, array) => index == 0 || value >= array[index])
console.log(`${name} 耗时 ${performance.now() - start} ${isOrder ? '排序正确' : '排序错误'} ${result?.slice(0, 20)}`)
}
export { genRandomList, testCostTime }
Promise.all实现异步同时测试
- 用
genRandomList
统一生成的array
增加公平性 testAlgorithmTime
封装一层Promise
来异步调用- 用
Promise.all
包裹测试的promise
js
//index.js
import quickSort from './quick.js'
import mergeSort from './merge.js'
import heapSort from './heap.js'
import bubbleSort from './bubble.js'
import { testCostTime, genRandomList } from './testTime.js'
const array = genRandomList()
const testAlgorithmTime = (array, algorithm, name) => {
return new Promise(resolve => {
testCostTime(array, algorithm, name)
resolve()
})
}
Promise.all([
testAlgorithmTime(array, quickSort, 'quickSort'),
testAlgorithmTime(array, heapSort, 'heapSort'),
testAlgorithmTime(array, mergeSort, 'mergeSort'),
testAlgorithmTime(array, bubbleSort, 'bubbleSort'),
])
.then(e => {
console.log('测试完成')
})
.catch(error => {
console.error('出现错误:', error)
})
测试结果 🎉🎉🎉
符合预期 手工! ✨