数据结构与算法 -> 大顶堆与小顶堆
一、大顶堆
- 大顶堆是一种数据结构,它是一颗完全二叉树,并且满足以下性质:
- 每个节点的值都大于或等于它的子节点的值
- 因此,大顶堆的根节点(也称为堆顶)总是最大的元素
 
二、小顶堆
- 小顶堆也是一种数据结构,它是一颗完全二叉树,并且满足以下性质:
- 每个节点的值都小于或等于它的子节点的值
- 因此,小顶堆的根节点(也称为堆顶)总是最小的元素
 
三、主要区别
- 小顶堆和大顶堆是堆这种数据结构的两种形式,它们都是一颗完全二叉树,并且满足特定的性质。小顶堆的堆顶元素是最小的元素,而大顶堆的堆顶元素是最大的元素。小顶堆和大顶堆常用于实现优先队列,其操作的时间复杂度通常为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 ... 
随机推荐
- SpringBoot整合ES+Kibana
			前言:最近在写一个HTTP代理服务器,记录日志使用的是ES,所以涉及到SpringBoot和ES的整合,整合完毕后又涉及到数据可视化分析,所以使用了Kibana进行管理,有些坑,需要记录一下 Spri ... 
- 26.ViewSet和action
			在dispatch过程中,下列属性可用于 ViewSet : basename - 根url路径 action - 当前动作类型(例如 list , create ). detail - 用于指示 ... 
- 「MySQL高级篇」explain分析SQL,索引失效&&常见优化场景
			大家好,我是melo,一名大三后台练习生 专栏回顾 索引的原理&&设计原则 欢迎关注本专栏:MySQL高级篇 本篇速览 在我们上一篇文章中,讲到了索引的原理&&设计原则 ... 
- 鹅长微服务发现与治理巨作PolarisMesh实践-上
			@ 目录 概述 定义 核心功能 组件和生态 特色亮点 解决哪些问题 官方性能数据 架构原理 资源模型 服务治理 基本原理 服务注册 服务发现 安装 部署架构 集群安装 SpringCloud应用接入 ... 
- 题解 AT2361 [AGC012A] AtCoder Group Contest
			\(\sf{Solution}\) 显然要用到贪心的思想. 既然最终的结果只与每组强度第二大选手有关,那就考虑如何让他的值尽可能大. 其实,从小到大排个序就能解决,越靠后的值越大,使得每组强度第二大选 ... 
- QML 怎么调用 C++ 中的内容?
			以下内容为本人的学习笔记,如需要转载,请声明原文链接微信公众号「englyf」https://mp.weixin.qq.com/s/z_JlmNe6cYldNf11Oad_JQ 先说明一下测试环境 编 ... 
- .Net6新版本的AssemblyLoadContext 加载程序集和卸载程序集
			准备俩个项目 第一个是控制台 第二个项目是类库 类库项目中只有一个示例class 将类库的代码生成dll 并且设置属性为复制到输出目录 using System.Runtime.Loader; var ... 
- Java安全之CC3
			前言 上一篇文章学习了Java中加载字节码的⼀些⽅法,其中介绍了TemplatesImpl,TemplatesImpl 是⼀个可以加载字节码的类,通过调⽤其newTransformer()⽅法,即可执 ... 
- 【题解】CF1503B 3-Coloring
			题面传送门 解决思路 讲一下 \(\text{VP}\) 时的思路. 首先想到,只要能将棋盘中红色或蓝色部分全部填成同一个数,那么剩下的就不会受限了(可行有两个,限制只有一个): 但考虑到交互库可能有 ... 
- C#where关键字约束
			where关键字的用法 where关键词一个最重要的用法就是在泛型的声明.定义中做出约束. 约束又分为接口约束.基类约束.构造函数约束.函数方法的约束. 1.接口约束,泛型参数必须实现相应的接口才可以 ... 
