算法|最大堆、最小堆和堆排序的实现(JavaScript)

news2024/12/28 23:07:13

一些概念

  • 堆:特殊的完全二叉树,具有特定性质的完全二叉树。
  • 大根堆:父节点 > 子节点
  • 小根堆:父节点 < 子节点

二叉堆也属于完全二叉树,所以可以用数组表示。

  • 若下标从1开始,左节点为 2*i ,右节点为 2*i+1 ,父节点为 i//2
  • 若下标从1开始,左节点为 2*i+1 ,右节点为 2*i+1+2 ,父节点为 (i-1)//2
    image.png

最大堆

两个重要方法,插入元素和移出元素。

  • 插入元素:在堆尾插入元素,调用辅助方法,将该元素上浮到正确位置。
  • 移出元素:将堆尾元素删去并替换到堆首,将该元素下沉到正确位置。

解释:

  • 上浮:如果父节点更大,则替换,循环直至比父节点小。
  • 下沉:如果子节点中较大的那个更小,则替换,循环直至子节点都比自身小。

实现

class MaxHeap {
	constructor() {
		this.heap = []
	}
	isEmpty() {
		return this.heap.length === 0
	}
	size() {
		return this.heap.length
	}
	#getParentIndex(idx) {
		return Math.floor((idx-1)/2)
	}
	#getLeft(idx) {
		return idx * 2 + 1
	}
	#getRight(idx) {
		return idx * 2 + 2
	}
	// 插入
	insert(v) {
		this.heap.push(v)
		this.#swim(this.size()-1)
	}
	// 删除最大值
	deleteMax() {
		const max = this.heap[0]
		this.#swap(0, this.size() - 1) // 将根和最后一个元素交换
		this.heap.pop() // 防止对象游离
		this.#sink(0) // 下沉,恢复有序性
		return max
	}
	// 第i个是否小于第j个
	#compare(a, b) {
			return a < b
	}
	// 交换
	#swap(i, j) {
		[this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]]
	}
	// 上浮
	#swim(k) {
		let parent = this.#getParentIndex(k)
		while(k > 0 && this.#compare(this.heap[parent], this.heap[k])) {
			this.#swap(parent, k)
			k = parent
			parent = this.#getParentIndex(k)
		}
	}
	// 下沉
	#sink(k) {
		while (this.#getLeft(k) < this.size()) {
			let j = this.#getLeft(k)
			// j 指向子节点的较大值
			if (j+1 < this.size() && this.#compare(this.heap[j], this.heap[j+1])) j++
			// 如果子节点都小
			if (this.#compare(this.heap[j], this.heap[k])) break
			this.#swap(k, j)
			k = j
		}
	}
}

测试

const mh = new MaxHeap()
mh.insert(20)
mh.insert(80)
mh.insert(50)
mh.insert(40)
mh.insert(30)
mh.insert(40)
mh.insert(20)
mh.insert(10)
mh.insert(35)
mh.insert(15)
mh.insert(90)
console.log(mh.heap)
// [ <1 empty item>, 90, 80, 50, 35, 40, 40, 20, 10, 20, 15, 30 ]
mh.deleteMax()
mh.deleteMax()
mh.deleteMax()
console.log(mh.heap)
// [ <1 empty item>, 40, 35, 40, 20, 30, 15, 20, 10 ]

最小堆

与最小堆相比,仅是交换条件不同

实现

class MinHeap {
	constructor() {
		this.heap = []
	}
	isEmpty() {
		return this.heap.length === 0
	}
	size() {
		return this.heap.length
	}
	#getParentIndex(idx) {
		return Math.floor((idx-1)/2)
	}
	#getLeft(idx) {
		return idx * 2 + 1
	}
	#getRight(idx) {
		return idx * 2 + 2
	}
	// 插入
	insert(v) {
		this.heap.push(v)
		this.#swim(this.size()-1)
	}
	// 删除最大值
	deleteMin() {
		const max = this.heap[0]
		this.#swap(0, this.size() - 1) // 将根和最后一个元素交换
		this.heap.pop() // 防止对象游离
		this.#sink(0) // 下沉,恢复有序性
		return max
	}
	// 第i个是否小于第j个
	#compare(a, b) {
			return a > b
	}
	// 交换
	#swap(i, j) {
		[this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]]
	}
	// 上浮
	#swim(k) {
		let parent = this.#getParentIndex(k)
		while(k > 0 && this.#compare(this.heap[parent], this.heap[k])) {
			this.#swap(parent, k)
			k = parent
			parent = this.#getParentIndex(k)
		}
	}
	// 下沉
	#sink(k) {
		while (this.#getLeft(k) < this.size()) {
			let j = this.#getLeft(k)
			// j 指向子节点的较小值
			if (j+1 < this.size() && this.#compare(this.heap[j], this.heap[j+1])) j++
			// 如果子节点都大
			if (this.#compare(this.heap[j], this.heap[k])) break
			this.#swap(k, j)
			k = j
		}
	}
}

测试

const mh = new MinHeap()
mh.insert(20)
mh.insert(80)
mh.insert(50)
mh.insert(40)
mh.insert(30)
mh.insert(40)
mh.insert(20)
mh.insert(10)
mh.insert(35)
mh.insert(15)
mh.insert(90)
console.log(mh.heap)
// [10, 15, 20, 30, 20, 50, 40, 80, 35, 40, 90]
mh.deleteMin()
mh.deleteMin()
mh.deleteMin()
console.log(mh.heap)
// [20, 30, 40, 35, 40, 50, 90, 80]

堆(自定义比较函数)

默认为最大堆,根据元素的大小进行排序,可自定义排序规则,返回值为布尔值。

class Heap {
	constructor(compareFn) {
		this.heap = []
		this.compare = (typeof compareFn === 'function') ? compareFn : this.#defaultCompare
	}
	isEmpty() {
		return this.heap.length === 0
	}
	size() {
		return this.heap.length
	}
	#getParentIndex(idx) {
		return Math.floor((idx-1)/2)
	}
	#getLeft(idx) {
		return idx * 2 + 1
	}
	#getRight(idx) {
		return idx * 2 + 2
	}
	// 插入
	insert(v) {
		this.heap.push(v)
		this.#swim(this.size()-1)
	}
	// 删除最大值
	delete() {
		const max = this.heap[0]
		this.#swap(0, this.size() - 1) // 将根和最后一个元素交换
		this.heap.pop() // 防止对象游离
		this.#sink(0) // 下沉,恢复有序性
		return max
	}
	// 第i个是否小于第j个
	#defaultCompare(a, b) {
			return a < b
	}
	// 交换
	#swap(i, j) {
		[this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]]
	}
	// 上浮
	#swim(k) {
		let parent = this.#getParentIndex(k)
		while(k > 0 && this.compare(this.heap[parent], this.heap[k])) {
			this.#swap(parent, k)
			k = parent
			parent = this.#getParentIndex(k)
		}
	}
	// 下沉
	#sink(k) {
		while (this.#getLeft(k) < this.size()) {
			let j = this.#getLeft(k)
			// j 指向子节点的较大值
			if (j+1 < this.size() && this.compare(this.heap[j], this.heap[j+1])) j++
			// 如果子节点都小
			if (this.compare(this.heap[j], this.heap[k])) break
			this.#swap(k, j)
			k = j
		}
	}
}

测试

const mh = new Heap((a,b)=>a.val<b.val)
mh.insert({val: 20})
mh.insert({val: 45})
mh.insert({val: 56})
mh.insert({val: 12})
mh.insert({val: 93})
mh.insert({val: 34})
mh.insert({val: 12})
mh.insert({val: 84})
console.log(mh.heap)
// [
//   { val: 93 },
//   { val: 84 },
//   { val: 45 },
//   { val: 56 },
//   { val: 20 },
//   { val: 34 },
//   { val: 12 },
//   { val: 12 }
// ]
mh.delete()
mh.delete()
console.log(mh.heap)
// [
//   { val: 56 },
//   { val: 20 },
//   { val: 45 },
//   { val: 12 },
//   { val: 12 },
//   { val: 34 }
// ]

堆排序

(1)先原地创建一个最大堆,因为叶子节点没有子节点,因此只需要对非叶子节点从右向左进行下沉操作

(2)把堆首(堆的最大值)和堆尾替换位置,堆大小减一,保持非堆是递增的,保持数组最后一个元素是最大的,最后对堆首进行下沉操作(或者说把非堆重新堆化)。

(3)重复第二步直至清空堆。

注意:排序的数组第一个元素下标是 0 ,跟上面处理边界不一样。

实现

function heapSort (arr) {
	// arr = arr.slice(0) // 是否原地排序
	let N = arr.length - 1
	if (!arr instanceof Array) {
		return null
	}else if (arr instanceof Array && (N === 0 || N === -1) ) {
		return arr
	}
	function exch(i, j) {
		[arr[i], arr[j]] = [arr[j], arr[i]]
	}
	function less(i, j) {
		return arr[i] < arr[j]
	}
	function sink(k) {
		while (2 *k + 1 <= N) {
			let j = 2 * k + 1
			// j 指向子节点的较大值
			if (j+1 <= N && less(j, j+1)) {
				j++
			}
			// 如果子节点都小
			if (less(j, k)) break
			exch(k, j)
			k = j
		}
	}
	// 构建堆
	for(let i = Math.floor(N/2); i >= 0; i--) {
		sink(i)
	}
	// 堆有序
	while (N > 0) {
		exch(0, N--)
		sink(0)
	}
}

另一个实现

function heapSort (arr) {
	// arr = arr.slice(0) // 是否原地排序
	let N = arr.length
	if (!arr instanceof Array) {
		return null
	}else if (arr instanceof Array && (N === 0 || N === -1) ) {
		return arr
	}
	function getParentIndex(idx) {
		return Math.floor((idx-1)/2)
	}
	function getLeft(idx) {
		return idx * 2 + 1
	}
	function getRight(idx) {
		return idx * 2 + 2
	}
	function swap(i, j) {
		[arr[i], arr[j]] = [arr[j], arr[i]]
	}
	function compare(i, j) {
		return i < j
	}
	function sink(k) {
		while (getLeft(k) < N) {
			let j = getLeft(k)
			// j 指向子节点的较大值
			if (j+1 < N && compare(arr[j], arr[j+1])) j++
			// 如果子节点都小
			if (compare(arr[j], arr[k])) break
			swap(k, j)
			k = j
		}
	}
	// 构建堆
	for(let i = Math.floor(N/2); i >= 0; i--) {
		sink(i)
	}
	// 堆有序
	while (N > 1) {
		swap(0, --N)
		sink(0)
	}
}

测试

const arr1 = [15, 20, 30, 35, 20, 50, 40, 80, 10, 40, 90]
heapSort(arr1)
console.log(arr1)
// [10, 15, 20, 20, 30, 35, 40, 40, 50, 80, 90]
const arr2 = [62, 88, 58, 47, 35, 73, 51, 99, 37, 93];
heapSort(arr2)
console.log(arr2)
// [35, 37, 47, 51, 58, 62, 73, 88, 93, 99]

参考

  1. algs4
  2. 【JS手写最小堆(小顶堆)、最大堆(大顶堆)】:https://juejin.cn/post/7128369000001568798
  3. 【数据结构与算法(4)——优先队列和堆】:https://zhuanlan.zhihu.com/p/39615266
  4. 【最大堆最小堆及堆排序】:https://mingshan.fun/2019/05/14/heap/
  5. 【搞定JavaScript算法系列–堆排序】:https://juejin.cn/post/6844903830258188296

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1612865.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java的Future机制详解

Java的Future机制详解 一、为什么出现Future机制二、Future的相关类图2.1 Future 接口2.2 FutureTask 类 三、FutureTask的使用方法四、FutureTask源码分析4.1 state字段4.2 其他变量4.4 构造函数4.5 run方法及其他 一、为什么出现Future机制 常见的两种创建线程的方式。一种是…

高架学习笔记之软件架构风格

目录 零、什么是软件架构风格 一、常见的软件架构风格 二、数据流风格 2.1. 批处理风格 2.2. 管道-过滤器风格 三、调用/返回风格 3.1. 主/子程序风格 3.2. 面向对象风格 3.3. 层次型风格 3.4. 客户端/服务器风格 3.4.1. 两层C/S体系结构 3.4.2. 三层C/S体系结构 …

MBD_入门篇_20_Simulink子系统

20.Simulink子系统 20.1 概述 Simulink的子系统&#xff0c;相当于代码的function函数&#xff0c;但是模型的子系统又不完全等效于代码的函数。虚拟子系统并不会生成函数&#xff0c;而是以代码块的形式放在相应的调用位置上。模型层面我们使用子系统去做模块化的设计&#xf…

Mini-Gemini: 探索多模态视觉语言模型的新境界

一、背景 在数字化时代&#xff0c;人工智能的发展正以前所未有的速度推进。特别是在多模态学习领域&#xff0c;结合视觉和语言的能力已成为研究的热点。最近&#xff0c;一篇名为“Mini-Gemini: Mining the Potential of Multi-modality Vision Language Models”的文章在arX…

[已解决]react打包部署

react打包部署 问题 npm install 命令无反应 思路 换成 yarn install 安装完hadoop的环境后&#xff0c;使用node的yarn会报错&#xff1a; 我们在cmd使用where yarn&#xff0c;如下&#xff1a; 看你想保留哪一个&#xff0c;我平时node用的多&#xff0c;就把hadoop的y…

飞书API(5):查看多维表 28 种数据类型的数据结构

一、引入 前面我们用于测试的数据集其实都是比较常用的数据&#xff0c;比如说文本、数字、单选等&#xff0c;但飞书多维表并不仅仅只有这些数据&#xff0c;截止发文&#xff0c;飞书多维表应用上支持28种数据类型&#xff0c;在数据层面飞书官方只提供了23种数据类型&#…

Cadence软件安装

Cadence软件 iscape 用于安装cadence家的安装软件 解压缩安装包tar -xvf IScape04.23.tar.gz运行bash IScape/iscape/bin/iscape.sh 设置默认安装路径(可选)IC618 这里使用的是IC618.320版本作为示例,其他版本安装过程差不多 安装 首先安装终端模拟器,不然安装之后会失败…

【前端】校园二手书交易系统javascript+css+html (源码)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

Vnode是如何产生的?

源码 流程图 源码解读 Vue.js2.0中有两种生成方式&#xff1a;第一种是直接在Vue对象的option中添加render字段&#xff1b;第二种是像Vue.js 1.x版本那样写一个模板或者指定一个el根元素&#xff0c;它会首先转换成模板&#xff0c;经过HTMI语法解析器生成一个 ast 抽象语法树…

JAVAEE——IP协议

文章目录 IP协议IP协议报头格式IP协议报头的各个区段四位版本四位首部长度八位服务类型16位总长度16位标识&#xff0c;3位标志&#xff0c;13位片偏移八位生存时间八位协议 地址管理IP地址解决提议1&#xff1a;动态分配Ip地址解决提议2&#xff1a;NAT机制 IP协议 IP协议报头…

【新手入门必看】从零开始学指针

我使用VS CODEMSYS2的编译环境进行学习&#xff0c;想使用VS CODE进行C/C代码编写的小伙伴参考这篇文章进行环境配置VS Code 配置 C/C 编程运行环境&#xff08;保姆级教程&#xff09; 一、指针的引入 指针地址 #include <stdio.h>int main() {int a 10;printf(&quo…

编写函数fun,函数的功能是:根据以下公式计算s,计算结果作为函数值返回;n通过形参传入。

本文收录于专栏:算法之翼 https://blog.csdn.net/weixin_52908342/category_10943144.html 订阅后本专栏全部文章可见。 本文含有题目的题干、解题思路、解题思路、解题代码、代码解析。本文分别包含C语言、C++、Java、Python四种语言的解法完整代码和详细的解析。 题干 编写…

Java:二叉树(1)

从现在开始&#xff0c;我们进入二叉树的学习&#xff0c;二叉树是数据结构的重点部分&#xff0c;在了解这个结构之前&#xff0c;我们先来了解一下什么是树型结构吧&#xff01; 一、树型结构 1、树型结构简介 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>…

深度学习系列64:数字人openHeygen详解

1. 主流程分析 从inference.py函数进入&#xff0c;主要流程包括&#xff1a; 1&#xff09; 使用cv2获取视频中所有帧的列表&#xff0c;如下&#xff1a; 2&#xff09;定义Croper。核心代码为69行&#xff1a;full_frames_RGB, crop, quad croper.crop(full_frames_RGB)。…

openobserve-filebeat配置

优势 rustgolang开发的日志工具组合&#xff0c;自带日志数据存储&#xff0c;简化部署和管理。日志数据可配置保留x天。从日志文件中采集&#xff0c;做到非侵入式日志集中管理。 可从日志内容中提取信息进行报警等二次开发。 下载 openobserve-v0.10.1-windows-amd64 fil…

VL02N交货单清除字段:VLSTK(分配状态)

VL02N交货单清除字段&#xff1a;VLSTK(分配状态) 通过查找增强对应的BADI&#xff1a;LE_SHP_DELIVERY_PROC 修改方法&#xff1a;IF_EX_LE_SHP_DELIVERY_PROC~CHANGE_DELIVERY_HEADER&#xff0c;代码如下&#xff1a;

JSS作业

JSS作业&#xff1a; 1: <script>var cnt parseInt(window.prompt("请输入打印的行数&#xff1a;"));for (var i 1; i < cnt; i){for (var j 1; j < i; j){document.write("*")}document.write("<br>")} </script>…

XTuner 微调 LLM:1.8B、多模态、Agent

两种微调范式&#xff1a;增量预训练和指令微调 在大语言模型下游应用中&#xff0c;主要有两种微调范式&#xff1a;增量预训练和指令微调。增量预训练旨在让模型学习特定领域的常识&#xff0c;而不需要有监督标注的数据&#xff1b;指令微调则是通过高质量的对话数据训练模型…

C语言中, 文件包含处理,#include< > 与 #include ““的区别

文件包含处理 指一个源文件可以将另外一个文件的全部内容包含进来 &#xff23;语言提供了#include命令用来实现文件包含的操作 #include< > 与 #include ""的区别 <> 表示系统直接按系统指定的目录检索 "" 表示系统先在 "" 指定…

vulfocus靶场couchdb 权限绕过 (CVE-2017-12635)

Apache CouchDB是一个开源数据库&#xff0c;专注于易用性和成为"完全拥抱web的数据库"。它是一个使用JSON作为存储格式&#xff0c;JavaScript作为查询语言&#xff0c;MapReduce和HTTP作为API的NoSQL数据库。应用广泛&#xff0c;如BBC用在其动态内容展示平台&…