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

起因

写这篇文章的起因是看到网上有些写排序算法的代码写得比较冗长,而且不易懂,反正我有些看不懂,正好最近也在复习排序,于是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)
	})

测试结果 🎉🎉🎉

符合预期 手工! ✨

参考

相关推荐
懒大王爱吃狼42 分钟前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍
劲夫学编程43 分钟前
leetcode:杨辉三角
算法·leetcode·职场和发展
毕竟秋山澪1 小时前
孤岛的总面积(Dfs C#
算法·深度优先
浮生如梦_3 小时前
Halcon基于laws纹理特征的SVM分类
图像处理·人工智能·算法·支持向量机·计算机视觉·分类·视觉检测
励志成为嵌入式工程师5 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
逐·風5 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
捕鲸叉5 小时前
创建线程时传递参数给线程
开发语言·c++·算法
Devil枫5 小时前
Vue 3 单元测试与E2E测试
前端·vue.js·单元测试
A charmer5 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
wheeldown6 小时前
【数据结构】选择排序
数据结构·算法·排序算法