数据结构与算法 -> 大顶堆与小顶堆
一、大顶堆
- 大顶堆是一种数据结构,它是一颗完全二叉树,并且满足以下性质:
- 每个节点的值都大于或等于它的子节点的值
- 因此,大顶堆的根节点(也称为堆顶)总是最大的元素
二、小顶堆
- 小顶堆也是一种数据结构,它是一颗完全二叉树,并且满足以下性质:
- 每个节点的值都小于或等于它的子节点的值
- 因此,小顶堆的根节点(也称为堆顶)总是最小的元素
三、主要区别
小顶堆和大顶堆是堆这种数据结构的两种形式,它们都是一颗完全二叉树,并且满足特定的性质。小顶堆的堆顶元素是最小的元素,而大顶堆的堆顶元素是最大的元素。小顶堆和大顶堆常用于实现优先队列,其操作的时间复杂度通常为O(log n)。
小顶堆和大顶堆的区别在于它们的堆顶元素分别是最小的和最大的元素。因此,小顶堆通常用于求出数据集中的最小值,而大顶堆通常用于求出数据集中的最大值。
四、算法模板
1.大顶堆模板
// 大顶堆模板
class MaxHeap {
constructor() {
this.heap = [];
}
// 返回堆的大小
size() {
return this.heap.length;
}
// 向堆中插入一个新元素
insert(val) {
// 将新元素添加到堆的末尾
this.heap.push(val);
// 调整堆使其满足大顶堆的性质
this.heapifyUp();
}
// 删除堆顶元素
deleteTop() {
// 如果堆为空,则直接返回
if (this.size() === 0) return;
// 将堆顶元素与堆的最后一个元素交换
let temp = this.heap[0];
this.heap[0] = this.heap[this.size() - 1];
this.heap[this.size() - 1] = temp;
// 将堆的最后一个元素从堆中删除
this.heap.pop();
// 调整堆使其满足大顶堆的性质
this.heapifyDown();
}
// 调整堆使其满足大顶堆的性质
heapifyUp() {
// 获取新插入的元素的索引
let index = this.size() - 1;
// 循环,直到该元素的值大于等于它的父节点的值
while (index > 0 && this.heap[index] > this.heap[this.parent(index)]) {
// 将该元素与它的父节点交换
let temp = this.heap[index];
this.heap[index] = this.heap[this.parent(index)];
this.heap[this.parent(index)] = temp;
// 更新索引
index = this.parent(index);
}
}
// 调整堆使其满足大顶堆的性质
heapifyDown() {
// 获取堆顶元素的索引
let index = 0;
// 循环,直到该元素的值小于等于它的子节点的值
while (index < this.size() && this.heap[index] < this.maxChildValue(index)) {
// 获取该元素的子节点中的最大值的索引
let maxChildIndex = this.maxChildIndex(index);
// 将该元素与它的子节点中的最大值交换
let temp = this.heap[index];
this.heap[index] = this.heap[maxChildIndex];
this.heap[maxChildIndex] = temp;
// 更新索引
index = maxChildIndex;
}
}
// 返回给定索引的元素的父节点的索引
parent(index) {
return Math.floor((index - 1) / 2);
}
// 返回给定索引的元素的左子节点的索引
leftChild(index) {
return index * 2 + 1;
}
// 返回给定索引的元素的右子节点的索引
rightChild(index) {
return index * 2 + 2;
}
// 返回给定索引的元素的子节点中的最大值
maxChildValue(index) {
let leftChildIndex = this.leftChild(index);
let rightChildIndex = this.rightChild(index);
if (leftChildIndex >= this.size()) return -Infinity;
if (rightChildIndex >= this.size()) return this.heap[leftChildIndex];
return Math.max(this.heap[leftChildIndex], this.heap[rightChildIndex]);
}
// 返回给定索引的元素的子节点中的最大值的索引
maxChildIndex(index) {
let leftChildIndex = this.leftChild(index);
let rightChildIndex = this.rightChild(index);
if (leftChildIndex >= this.size()) return -1;
if (rightChildIndex >= this.size()) return leftChildIndex;
return this.heap[leftChildIndex] > this.heap[rightChildIndex] ? leftChildIndex : rightChildIndex;
}
}
2.小顶堆模板
// 小顶堆模板
class MinHeap {
constructor() {
// 初始化堆数组
this.heap = [];
}
// 获取父节点的索引
getParentIndex(childIndex) {
return Math.floor((childIndex - 1) / 2);
}
// 获取左子节点的索引
getLeftChildIndex(parentIndex) {
return 2 * parentIndex + 1;
}
// 获取右子节点的索引
getRightChildIndex(parentIndex) {
return 2 * parentIndex + 2;
}
// 判断是否存在父节点
hasParent(index) {
return this.getParentIndex(index) >= 0;
}
// 判断是否存在左子节点
hasLeftChild(index) {
return this.getLeftChildIndex(index) < this.heap.length;
}
// 判断是否存在右子节点
hasRightChild(index) {
return this.getRightChildIndex(index) < this.heap.length;
}
// 获取左子节点的值
leftChild(index) {
return this.heap[this.getLeftChildIndex(index)];
}
// 获取右子节点的值
rightChild(index) {
return this.heap[this.getRightChildIndex(index)];
}
// 获取父节点的值
parent(index) {
return this.heap[this.getParentIndex(index)];
}
// 交换堆中两个节点的值
swap(index1, index2) {
[this.heap[index1], this.heap[index2]] = [this.heap[index2], this.heap[index1]];
}
// 获取堆顶元素
peek() {
if (this.heap.length === 0) {
throw new Error('Heap is empty');
}
return this.heap[0];
}
// 取出堆顶元素,并重新排序
poll() {
if (this.heap.length === 0) {
throw new Error('Heap is empty');
}
const item = this.heap[0];
this.heap[0] = this.heap.pop();
this.heapifyDown();
return item;
}
// 向堆中插入新元素,并重新排序
add(item) {
this.heap.push(item);
this.heapifyUp();
}
// 从下往上重新排序
heapifyUp() {
let index = this.heap.length - 1;
// 只要当前节点有父节点,并且父节点的值比当前节点的值大,就交换它们的值
while (this.hasParent(index) && this.parent(index) > this.heap[index]) {
this.swap(this.getParentIndex(index), index);
index = this.getParentIndex(index);
}
}
// 从上往下重新排序
heapifyDown() {
let index = 0;
// 只要当前节点有左子节点
while (this.hasLeftChild(index)) {
let smallerChildIndex = this.getLeftChildIndex(index);
// 如果当前节点有右子节点,并且右子节点的值比左子节点的值小,就把右子节点的索引赋给smallerChildIndex
if (this.hasRightChild(index) && this.rightChild(index) < this.leftChild(index)) {
smallerChildIndex = this.getRightChildIndex(index);
}
// 如果当前节点的值已经比子节点的值小,就退出循环
if (this.heap[index] < this.heap[smallerChildIndex]) {
break;
} else {
// 否则交换它们的值,并继续循环
this.swap(index, smallerChildIndex);
}
index = smallerChildIndex;
}
}
}
五、力扣实操
1.大顶堆例题:第 327 场周赛T2 - 执行 K 次操作后的最大分数
代码如下:
/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/
var maxKelements = function(nums, k) {
let score = []
let heap = new MaxHeap()
for (let item of nums) {
heap.insert(item)
}
while (k) {
let max = heap.heap[0]
score.push(max)
max = Math.ceil(max / 3)
heap.heap[0] = max
heap.heapifyDown()
k--
}
return score.reduce((item, total) => { return item + total }, 0)
}; // 大顶堆模板
class MaxHeap {
constructor() {
this.heap = [];
} // 返回堆的大小
size() {
return this.heap.length;
} // 向堆中插入一个新元素
insert(val) {
// 将新元素添加到堆的末尾
this.heap.push(val);
// 调整堆使其满足大顶堆的性质
this.heapifyUp();
} // 删除堆顶元素
deleteTop() {
// 如果堆为空,则直接返回
if (this.size() === 0) return;
// 将堆顶元素与堆的最后一个元素交换
let temp = this.heap[0];
this.heap[0] = this.heap[this.size() - 1];
this.heap[this.size() - 1] = temp;
// 将堆的最后一个元素从堆中删除
this.heap.pop();
// 调整堆使其满足大顶堆的性质
this.heapifyDown();
} // 调整堆使其满足大顶堆的性质
heapifyUp() {
// 获取新插入的元素的索引
let index = this.size() - 1;
// 循环,直到该元素的值大于等于它的父节点的值
while (index > 0 && this.heap[index] > this.heap[this.parent(index)]) {
// 将该元素与它的父节点交换
let temp = this.heap[index];
this.heap[index] = this.heap[this.parent(index)];
this.heap[this.parent(index)] = temp;
// 更新索引
index = this.parent(index);
}
} // 调整堆使其满足大顶堆的性质
heapifyDown() {
// 获取堆顶元素的索引
let index = 0;
// 循环,直到该元素的值小于等于它的子节点的值
while (index < this.size() && this.heap[index] < this.maxChildValue(index)) {
// 获取该元素的子节点中的最大值的索引
let maxChildIndex = this.maxChildIndex(index);
// 将该元素与它的子节点中的最大值交换
let temp = this.heap[index];
this.heap[index] = this.heap[maxChildIndex];
this.heap[maxChildIndex] = temp;
// 更新索引
index = maxChildIndex;
}
} // 返回给定索引的元素的父节点的索引
parent(index) {
return Math.floor((index - 1) / 2);
} // 返回给定索引的元素的左子节点的索引
leftChild(index) {
return index * 2 + 1;
} // 返回给定索引的元素的右子节点的索引
rightChild(index) {
return index * 2 + 2;
} // 返回给定索引的元素的子节点中的最大值
maxChildValue(index) {
let leftChildIndex = this.leftChild(index);
let rightChildIndex = this.rightChild(index);
if (leftChildIndex >= this.size()) return -Infinity;
if (rightChildIndex >= this.size()) return this.heap[leftChildIndex];
return Math.max(this.heap[leftChildIndex], this.heap[rightChildIndex]);
} // 返回给定索引的元素的子节点中的最大值的索引
maxChildIndex(index) {
let leftChildIndex = this.leftChild(index);
let rightChildIndex = this.rightChild(index);
if (leftChildIndex >= this.size()) return -1;
if (rightChildIndex >= this.size()) return leftChildIndex;
return this.heap[leftChildIndex] > this.heap[rightChildIndex] ? leftChildIndex : rightChildIndex;
}
}
2.小顶堆例题:面试题 17.14. 最小K个数 - 力扣(LeetCode)
代码如下:
/**
* @param {number[]} arr
* @param {number} k
* @return {number[]}
*/
var smallestK = function(arr, k) {
let res = []
let heap = new MinHeap()
for (let item of arr) {
heap.add(item)
}
while (k) {
let min = heap.heap[0]
res.push(min)
heap.heap[0] = Number.MAX_VALUE
min = heap.heap[0]
heap.heapifyDown()
k--
}
return res
}; // 小顶堆模板
class MinHeap {
constructor() {
// 初始化堆数组
this.heap = [];
} // 获取父节点的索引
getParentIndex(childIndex) {
return Math.floor((childIndex - 1) / 2);
} // 获取左子节点的索引
getLeftChildIndex(parentIndex) {
return 2 * parentIndex + 1;
} // 获取右子节点的索引
getRightChildIndex(parentIndex) {
return 2 * parentIndex + 2;
} // 判断是否存在父节点
hasParent(index) {
return this.getParentIndex(index) >= 0;
} // 判断是否存在左子节点
hasLeftChild(index) {
return this.getLeftChildIndex(index) < this.heap.length;
} // 判断是否存在右子节点
hasRightChild(index) {
return this.getRightChildIndex(index) < this.heap.length;
} // 获取左子节点的值
leftChild(index) {
return this.heap[this.getLeftChildIndex(index)];
} // 获取右子节点的值
rightChild(index) {
return this.heap[this.getRightChildIndex(index)];
} // 获取父节点的值
parent(index) {
return this.heap[this.getParentIndex(index)];
} // 交换堆中两个节点的值
swap(index1, index2) {
[this.heap[index1], this.heap[index2]] = [this.heap[index2], this.heap[index1]];
} // 获取堆顶元素
peek() {
if (this.heap.length === 0) {
throw new Error('Heap is empty');
}
return this.heap[0];
} // 取出堆顶元素,并重新排序
poll() {
if (this.heap.length === 0) {
throw new Error('Heap is empty');
}
const item = this.heap[0];
this.heap[0] = this.heap.pop();
this.heapifyDown();
return item;
} // 向堆中插入新元素,并重新排序
add(item) {
this.heap.push(item);
this.heapifyUp();
} // 从下往上重新排序
heapifyUp() {
let index = this.heap.length - 1;
// 只要当前节点有父节点,并且父节点的值比当前节点的值大,就交换它们的值
while (this.hasParent(index) && this.parent(index) > this.heap[index]) {
this.swap(this.getParentIndex(index), index);
index = this.getParentIndex(index);
}
} // 从上往下重新排序
heapifyDown() {
let index = 0;
// 只要当前节点有左子节点
while (this.hasLeftChild(index)) {
let smallerChildIndex = this.getLeftChildIndex(index);
// 如果当前节点有右子节点,并且右子节点的值比左子节点的值小,就把右子节点的索引赋给smallerChildIndex
if (this.hasRightChild(index) && this.rightChild(index) < this.leftChild(index)) {
smallerChildIndex = this.getRightChildIndex(index);
}
// 如果当前节点的值已经比子节点的值小,就退出循环
if (this.heap[index] < this.heap[smallerChildIndex]) {
break;
} else {
// 否则交换它们的值,并继续循环
this.swap(index, smallerChildIndex);
}
index = smallerChildIndex;
}
}
}
数据结构与算法 -> 大顶堆与小顶堆的更多相关文章
- 堆排序(大顶堆、小顶堆)----C语言
堆排序 之前的随笔写了栈(顺序栈.链式栈).队列(循环队列.链式队列).链表.二叉树,这次随笔来写堆 1.什么是堆? 堆是一种非线性结构,(本篇随笔主要分析堆的数组实现)可以把堆看作一个数组,也可以被 ...
- HDU 3791 二叉搜索树 (数据结构与算法实验题 10.2 小明) BST
传送门:http://acm.hdu.edu.cn/showproblem.php?pid=3791 中文题不说题意. 建立完二叉搜索树后进行前序遍历或者后序遍历判断是否一样就可以了. 跟这次的作业第 ...
- 《排序算法》——堆排序(大顶堆,小顶堆,Java)
十大算法之堆排序: 堆的定义例如以下: n个元素的序列{k0,k1,...,ki,-,k(n-1)}当且仅当满足下关系时,称之为堆. " ki<=k2i,ki<=k2i+1;或k ...
- 数据结构:堆排序 (python版) 小顶堆实现从大到小排序 | 大顶堆实现从小到大排序
#!/usr/bin/env python # -*- coding:utf-8 -*- ''' Author: Minion-Xu 小堆序实现从大到小排序,大堆序实现从小到大排序 重点的地方:小堆序 ...
- heap c++ 操作 大顶堆、小顶堆
在C++中,虽然堆不像 vector, set 之类的有已经实现的数据结构,但是在 algorithm.h 中实现了一些相关的模板函数.下面是一些示例应用 http://www.cplusplus.c ...
- 大顶堆与小顶堆应用---寻找前k小数
vector<int> getLeastNumber(vector<int>& arr,int k){ vector<int> vec(k,); if(== ...
- 378. Kth Smallest Element in a Sorted Matrix(大顶堆、小顶堆)
Given a n x n matrix where each of the rows and columns are sorted in ascending order, find the kth ...
- wikioi 2573 大顶堆与小顶堆并用
题目描写叙述 Description 我们使用黑匣子的一个简单模型.它能存放一个整数序列和一个特别的变量i.在初始时刻.黑匣子为空且i等于0. 这个黑匣子能运行一系列的命令.有两类命令: ADD(x) ...
- 数据结构与算法(Java版)_堆
完全二叉树叫做堆. 完全二叉树就是最后一个节点之前不允许有不满的节点,就是不允许有空洞. 可以使用数组来做完全二叉树(堆). 堆分为大顶堆和小顶堆.大顶堆就是根节点上的数字是最大的,小顶堆就是根节点上 ...
- Java数据结构和算法(五)二叉排序树(BST)
Java数据结构和算法(五)二叉排序树(BST) 数据结构与算法目录(https://www.cnblogs.com/binarylei/p/10115867.html) 二叉排序树(Binary S ...
随机推荐
- 1NF | 2NF | 3NF的区分以及什么是函数依赖、部分函数依赖、值传递依赖(最详细的讲解1NF、2NF、3NF的关系)
1NF | 2NF | 3NF的区分以及什么是函数依赖.部分函数依赖.值传递依赖 符合3NF一定符合2NF.一定符合1IF 简单区分.2NF不存在部分函数依赖,3NF不存在传递函数依赖 第一范式1NF ...
- 齐博x1标签动态调用数据
示例代码如下: {qb:tag name="news_list_page_listdata02" type="cms" union="fid" ...
- Vue3的新特性
总概 1) 性能提升 打包大小减少 41% 初次渲染快 55%,更新渲染快 133% 内存减少 54% 使用 Proxy 代替 defineProperty 实现数据响应式 重写虚拟 DOM 的实现和 ...
- go:快速添加接口方法及其实现
问题描述 在大型项目中,通常存在多个模块,模块对外暴露的功能通常是通过接口封装,这样可以明确模块的功能,有效降低模块与模块之间的耦合度,同时模块与模块之间进行合理的组装.接口的实现,有时可能存在多个实 ...
- 【炫丽】从0开始做一个WPF+Blazor对话小程序
大家好,我是沙漠尽头的狼. .NET是免费,跨平台,开源,用于构建所有应用的开发人员平台. 本文演示如何在WPF中使用Blazor开发漂亮的UI,为客户端开发注入新活力. 注 要使WPF支持Blazo ...
- .NET周报【11月第1期 2022-11-07】
国内文章 开源·安全·赋能 - .NET Conf China 2022 https://mp.weixin.qq.com/s/_tYpfPeQgyEGsnR4vVLzHg .NET Conf Chi ...
- (译)TDD(测试驱动开发)的5个步骤
原文:5 steps of test-driven development https://developer.ibm.com/articles/5-steps-of-test-driven-deve ...
- Vue.js3.0练习题
第一章:Vue 3.0 概述 1.选择题 1.1.在MVVM设计模式中,Model代表的是_______. A. 数据模型 B. 控制器 C. 视图 D.监听模型 ...
- vim快捷键及命令大全
定位光标: G 将光标定位到文本末尾行首 gg 将光标定位到文本启始位置 0 (这个是零)定位到光标所在行行首 $ 定位到光标所在行行尾 数字G 跳转到第n行 移动光标: h 向左移动 l 向右移动 ...
- 移动 VR 开发时要避免的 PC 渲染技术
更新:本文是为 Quest 1 开发人员编写的.虽然 Quest 2 建立在相同的架构上,但现在更容易为阴影贴图(以及其他需要从先前渲染过程中生成的纹理读取的简单技术)做预算. 尽管移动芯片组可以支持 ...