挑战全网代码量最短且易懂的快排 归并 堆排!

起因

写这篇文章的起因是看到网上有些写排序算法的代码写得比较冗长,而且不易懂,反正我有些看不懂,正好最近也在复习排序,于是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更新则交换元素 并继续向下调整
    • 建堆 从最后一个非叶子节点调整
    • 形成有序堆 每次把堆顶和最后一个元素交换,有序序列从最后一个元素开始增加 并重新调整堆 最后整体有序
  • 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)
	})

测试结果 🎉🎉🎉

符合预期 手工! ✨

参考

相关推荐
白兰地空瓶18 小时前
🚀 10 分钟吃透 CSS position 定位!从底层原理到避坑实战,搞定所有布局难题
前端·css
onthewaying18 小时前
在Android平台上使用Three.js优雅的加载3D模型
android·前端·three.js
冴羽19 小时前
能让 GitHub 删除泄露的苹果源码还有 8000 多个相关仓库的 DMCA 是什么?
前端·javascript·react.js
悟能不能悟19 小时前
jsp怎么拿到url参数
java·前端·javascript
高山上有一只小老虎19 小时前
字符串字符匹配
java·算法
程序猿小蒜19 小时前
基于SpringBoot的企业资产管理系统开发与设计
java·前端·spring boot·后端·spring
Mapmost19 小时前
零代码+三维仿真!实现自然灾害的可视化模拟与精准预警
前端
程序猿_极客19 小时前
JavaScript 的 Web APIs 入门到实战全总结(day7):从数据处理到交互落地的全链路实战(附实战案例代码)
开发语言·前端·javascript·交互·web apis 入门到实战
suzumiyahr19 小时前
用awesome-digital-human-live2d创建属于自己的数字人
前端·人工智能·后端
愚润求学19 小时前
【动态规划】专题完结,题单汇总
算法·leetcode·动态规划