堆(Heap)的原理与C++实现

1. 什么是堆?

堆(Heap)是一种特殊的树形数据结构,通常用于实现优先队列。堆可以分为两种类型:

  • 最大堆(Max Heap):每个节点的值都大于或等于其子节点的值。
  • 最小堆(Min Heap):每个节点的值都小于或等于其子节点的值。

堆通常是一个完全二叉树,这意味着除了最后一层,其他层都是完全填满的,并且最后一层的节点都尽可能地靠左排列。

2. 堆的性质

  • 完全二叉树:堆是一个完全二叉树,这意味着它可以用数组来高效地表示。
  • 堆序性质:在最大堆中,父节点的值总是大于或等于其子节点的值;在最小堆中,父节点的值总是小于或等于其子节点的值。

Tips: 堆是完全二叉树,并非二叉搜索树

在数据结构中,完全二叉树二叉搜索树是两种常见的树形结构,它们虽然都属于二叉树的范畴,但在定义、性质和应用场景上有显著的区别。下面我们将详细分析它们的区别。

特性完全二叉树二叉搜索树
定义节点从上到下、从左到右依次填充左子树 < 根节点 < 右子树
有序性不一定有序中序遍历结果有序
结构要求必须是完全填充的(最后一层靠左)无特殊结构要求,只需满足有序性
查找效率不支持高效查找支持高效查找(平衡时 O(log n))
插入和删除通常用于堆操作,不支持动态插入删除支持动态插入和删除
应用场景堆、优先队列查找、排序、数据库索引
数组表示可以用数组高效表示通常用指针或引用表示

完全二叉树示例

        10/  \5    15/ \   /2   7 12
  • 节点从上到下、从左到右依次填充。
  • 最后一层的节点靠左排列。

二叉搜索树示例

        10/  \5    15/ \   / \2   7 12 20
  • 左子树的所有节点值小于根节点,右子树的所有节点值大于根节点。
  • 中序遍历结果为 [2, 5, 7, 10, 12, 15, 20],是一个有序序列。

3. 堆的操作

堆的主要操作包括:

  • 插入(Insert):将一个新元素插入堆中,并保持堆的性质。
  • 删除(Delete):删除元素,并保持堆的性质。
  • 查询(Query):查询堆顶元素
  • 构建堆(Build Heap):将一个无序数组构建成一个堆。

4. 堆的实现

堆通常使用数组来实现。在从数组下标0开始存储的堆,对于一个索引为 i 的节点:

  • 其父节点的索引为 (i - 1) / 2
  • 其左子节点的索引为 2 * i + 1
  • 其右子节点的索引为 2 * i + 2

4.1 堆的性质维护

堆的插入过程

假设我们有一个最大堆,初始堆为:[100, 19, 36, 17, 3, 25, 1, 2, 7],其对应的完全二叉树结构如下(用数组表示):

        100/     \19       36/  \     /  \17   3   25   1/ \
2  7

插入一个新元素40
将新元素添加到堆的末尾:堆的数组表示为 [100, 19, 36, 17, 3, 25, 1, 2, 7, 40],对应的完全二叉树结构如下:

     100/      \19      36/   \    / \17    3  25  1
/ \    /
2  7  40
  1. 向上调整(上浮):从新插入的节点开始,与其父节点比较。如果当前节点的值大于父节点的值,则交换它们的位置。
  • 40 的父节点是 3,40 > 3,交换它们的位置:
        100/      \19       36/  \     /  \17   40  25   1/ \   /2  7  3
  • 40 的新父节点是 19,40 > 19,交换它们的位置:
      100/      \40       36/  \     /  \
17   19  25   1
/ \   /
2  7 3
  • 40 的新父节点是 100,40 < 100,停止调整。
  • 最终,插入 40 后的堆为:[100, 40, 36, 17, 19, 25, 1, 2, 7, 3]

总结:堆在插入元素后,需要进行上浮(up)操作,是不断与父节点比较,若父节点小于当前节点,则交换位置。具体代码实现示例如下:

//存储下标从1开始,以大根堆为例
void heap_up(int idx){while(idx != 1){int parent = idx >> 1;if(heap[parent] < heap[idx]){swap(heap[parent], heap[idx]);idx = parent;}else{break;}}
}
堆的删除过程

假设我们从上述堆中删除最大值(堆顶元素 100)。

  1. 将堆顶元素与最后一个元素交换:交换 100 和 3 的位置,得到 [3, 40, 36, 17, 19, 25, 1, 2, 7, 100],然后删除最后一个元素(100),得到 [3, 40, 36, 17, 19, 25, 1, 2, 7]。这是因为我们在用数组存储堆的时候,头部元素的删除面临整个数组的移动,相当消耗计算资源,于是我们选择将头部元素和尾部元素进行交换,进行删除尾部,再调整堆
  2. 向下调整(下沉):从堆顶开始,比较当前节点与其子节点的值,将当前节点与较大的子节点交换,直到满足堆的性质。
  • 当前堆顶是 3,其子节点是 40 和 36,40 > 36,选择 40 与 3 交换得到:
      40/      \3       36/  \     /  \
17   19  25   1
/ \  
2  7 
  • 3 的新位置是左子树的根,其子节点是 17 和 19,19 > 17,选择 19 与 3 交换:
      40/      \19       36/  \     /  \
17   3  25   1
/ \  
2  7 
  • 最终,删除 100 后的堆为:[40, 19, 36, 17, 3, 25, 1, 2, 7]
void heap_down(int idx){int size = top;while(1){int leftChild = idx * 2;int rightChild = idx * 2 + 1;int largest = idx;if(leftChild < size && heap[largest] < heap[leftChild]){largest = leftChild;}if(rightChild < size && heap[largest] < heap[rightChild]){largest = rightChild;}if (largest != idx) {swap(heap[idx], heap[largest]);idx = largest;} else {break;}}
}

4.2 C++ 实现最大堆

这里展示一个封装成对象的大根堆

#include <iostream>
#include <vector>
#include <algorithm>class MaxHeap {
private:std::vector<int> heap;void heapifyUp(int index) {while (index > 0) {int parentIndex = (index - 1) / 2;if (heap[index] > heap[parentIndex]) {std::swap(heap[index], heap[parentIndex]);index = parentIndex;} else {break;}}}void heapifyDown(int index) {int size = heap.size();while (true) {int leftChild = 2 * index + 1;int rightChild = 2 * index + 2;int largest = index;if (leftChild < size && heap[leftChild] > heap[largest]) {largest = leftChild;}if (rightChild < size && heap[rightChild] > heap[largest]) {largest = rightChild;}if (largest != index) {std::swap(heap[index], heap[largest]);index = largest;} else {break;}}}public:void insert(int value) {heap.push_back(value);heapifyUp(heap.size() - 1);}int extractMax() {if (heap.empty()) {throw std::out_of_range("Heap is empty");}int maxValue = heap[0];heap[0] = heap.back();heap.pop_back();heapifyDown(0);return maxValue;}void buildHeap(const std::vector<int>& array) {heap = array;for (int i = (heap.size() / 2) - 1; i >= 0; --i) {heapifyDown(i);}}void printHeap() const {for (int value : heap) {std::cout << value << " ";}std::cout << std::endl;}
};int main() {MaxHeap maxHeap;maxHeap.insert(10);maxHeap.insert(20);maxHeap.insert(15);maxHeap.insert(30);maxHeap.insert(40);std::cout << "Max Heap: ";maxHeap.printHeap();std::cout << "Extracted Max: " << maxHeap.extractMax() << std::endl;std::cout << "Max Heap after extraction: ";maxHeap.printHeap();std::vector<int> array = {5, 3, 8, 1, 9};maxHeap.buildHeap(array);std::cout << "Max Heap built from array: ";maxHeap.printHeap();return 0;
}

4.2 代码解析

  • heapifyUp:用于在插入新元素后,从下往上调整堆,确保堆的性质。
  • heapifyDown:用于在删除堆顶元素后,从上往下调整堆,确保堆的性质。
  • insert:将新元素插入堆中,并调用 heapifyUp 进行调整。
  • extractMax:删除并返回堆顶元素,然后调用 heapifyDown 进行调整。
  • buildHeap:将一个无序数组构建成一个堆。

5. 堆的应用

  • 优先队列:堆是实现优先队列的理想数据结构,因为可以快速获取和删除最大或最小元素。
  • 堆排序:堆排序是一种基于堆的比较排序算法,时间复杂度为 O(n log n)。
  • Dijkstra算法:在图的单源最短路径算法中,堆用于高效地选择下一个要处理的节点。

6. 总结

堆是一种非常高效的数据结构,特别适用于需要频繁获取最大或最小元素的场景。通过数组实现堆,可以充分利用其完全二叉树的性质,使得插入、删除和构建堆的操作都能在 O(log n) 的时间内完成。

7.练习

ACWing模拟堆
这个题相较普通的堆,增添了一个需要维护的对象,就是这个数字是第几次插入的。所以我们需要额外维护两个数组posinv_pos,分别表示第k个插入的数在堆数组中的索引,和堆数组中第i个数是第几个插入的,完整代码如下:

#include <iostream>
#include <cstring>
#include <string>
using namespace std;
const int maxn = 1e5 + 7;
int heap[maxn], top;
int pos[maxn], inv_pos[maxn];
void heap_swap(int a, int b){swap(heap[a], heap[b]);swap(pos[inv_pos[a]], pos[inv_pos[b]]);swap(inv_pos[a], inv_pos[b]);
}
void down(int idx){while(1){int leftChild = idx * 2;int rightChild = idx * 2 + 1;int smallest = idx;if(leftChild <= top && heap[leftChild] < heap[smallest])smallest = leftChild;if(rightChild <= top && heap[rightChild] < heap[smallest])smallest = rightChild;if(idx != smallest) {heap_swap(idx, smallest);idx = smallest;}elsebreak;}
}
void up(int idx){while(idx != 1){int parent = idx >> 1;if(heap[parent] > heap[idx]){heap_swap(idx, parent);idx = parent;}else{break;}}
}
int main() {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int n;cin>>n;string op;int x, k, cnt = 0;while(n--){cin>>op;if(op == "I"){cin>>x;heap[++top] = x;pos[++cnt] = top;inv_pos[top] = cnt;up(top);}else if(op == "PM"){cout<<heap[1]<<endl;}else if(op == "DM"){heap_swap(1, top);top--;down(1);}else if(op == "D"){cin>>k;int now = pos[k];heap_swap(now, top);top --;down(now);up(now);}else if(op == "C"){cin>>k>>x;heap[pos[k]] = x;up(pos[k]);down(pos[k]);}}return 0;
}

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

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

相关文章

移除元素-双指针(下标)

题目 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。 假设 nums 中不等于 val 的元素数量为 k&#xff0c;要通过此题&#xff0c;您需要执行以下操作&#xff1a…

log4j2日志配置文件

log4j2配置文件每个项目都会用到,记录一个比较好用的配置文件,方便以后使用时调取,日志输出级别为debug,也可以修改 <?xml version"1.0" encoding"UTF-8"?> <Configuration monitorInterval"180" packages""><prope…

高等代数笔记—映射与线性空间

映射 映射&#xff1a; σ : M → M ′ \sigma: M \to M σ:M→M′ σ ( a ) a ′ , a ∈ M , a ′ ∈ M ′ \sigma(a)a, a\in M, a \in M σ(a)a′,a∈M,a′∈M′ a ′ a a′是 a a a在 σ \sigma σ下的像&#xff0c; a a a是 a ′ a a′在 σ \sigma σ下的原像 σ : …

提示词实践总结

目录 一、要求创建SqlServer表&#xff08;ChatGpt&#xff09; 二、要求生成多层架构代码&#xff08;Cursor&#xff09; 三、要求修改方法返回值类型&#xff08;Cursor&#xff09; 四、要求修改方法入参&#xff08;Cursor&#xff09; 五、复杂的多表关联生成&#…

java进阶文章链接

java 泛型&#xff1a;java 泛型详解-绝对是对泛型方法讲解最详细的&#xff0c;没有之一 Java 泛型&#xff0c;你了解类型擦除吗&#xff1f; java 注解&#xff1a;深入理解Java注解类型 秒懂&#xff0c;Java 注解 &#xff08;Annotation&#xff09;你可以这样学 jav…

MyBatis-Plus笔记-快速入门

大家在日常开发中应该能发现&#xff0c;单表的CRUD功能代码重复度很高&#xff0c;也没有什么难度。而这部分代码量往往比较大&#xff0c;开发起来比较费时。 因此&#xff0c;目前企业中都会使用一些组件来简化或省略单表的CRUD开发工作。目前在国内使用较多的一个组件就是…

Maven jar 包下载失败问题处理

Maven jar 包下载失败问题处理 1.配置好国内的Maven源2.重新下载3. 其他问题 1.配置好国内的Maven源 打开⾃⼰的 Idea 检测 Maven 的配置是否正确&#xff0c;正确的配置如下图所示&#xff1a; 检查项⼀共有两个&#xff1a; 确认右边的两个勾已经选中&#xff0c;如果没有请…

Spring 核心技术解析【纯干货版】- IX:Spring 数据访问模块 Spring-Jdbc 模块精讲

在现代企业级应用中&#xff0c;数据访问层的稳定性和高效性至关重要。为了简化和优化数据库操作&#xff0c;Spring Framework 提供了 Spring-JDBC 模块&#xff0c;旨在通过高度封装的 JDBC 操作&#xff0c;简化开发者的编码负担&#xff0c;减少冗余代码&#xff0c;同时提…

探秘AI的两大核心:决策式AI与生成式AI‌

目录 一、引言 二、从定义上来看 1. 决策式AI&#xff08;Discriminative AI&#xff09; 2. 生成式AI&#xff08;Generative AI&#xff09; 三、从技术原理上来看 1. 决策式AI&#xff08;Discriminative AI&#xff09; 2. 生成式AI&#xff08;Generative AI&#…

2.5学习

misc buuctf-假如给我三天光明 下载附件后得到了一个压缩包和一个图片&#xff0c;压缩包为加密压缩包&#xff0c;需要解出密码&#xff0c;然后注意到这个图片并非简单的一个封面&#xff0c;在下方还有诸多点&#xff0c;有黑有灰。经过搜索&#xff0c;发现这是盲文通过与…

sed变量中特殊字符/处理方式

个人博客地址&#xff1a;sed变量中特殊字符/处理方式 | 一张假钞的真实世界 如果变量值中包含斜杠&#xff08;/&#xff09;特殊字符&#xff0c;在使用sed命令的做行内字符串替换时可以使用井号&#xff08;#&#xff09;做为sed语法分隔符&#xff0c;如下&#xff1a; G…

java进阶1——JVM

java进阶——JVM 1、JVM概述 作用 Java 虚拟机就是二进制字节码的运行环境&#xff0c;负责装载字节码到其内部&#xff0c;解释/编译为对 应平台上的机器码指令行&#xff0c;每一条 java 指令&#xff0c;java 虚拟机中都有详细定义&#xff0c;如怎么取操 作数&#xff0c…

搭建集成开发环境PyCharm

1.下载安装Python&#xff08;建议下载并安装3.9.x&#xff09; https://www.python.org/downloads/windows/ 要注意勾选“Add Python 3.9 to PATH”复选框&#xff0c;表示将Python的路径增加到环境变量中 2.安装集成开发环境Pycharm http://www.jetbrains.com/pycharm/…

vue2-v-if和v-for的优先级

vue2-v-if和v-for的优先级 1.v-if和v-for的作用 v-if是条件渲染&#xff0c;只有条件表达式true的情况下&#xff0c;才会渲染v-for是基于一个数组来渲染一个列表&#xff0c;在v-for的时候&#xff0c;保证给每个元素添加独一无二的key值&#xff0c;便于diff算法进行优化 …

通过C/C++编程语言实现“数据结构”课程中的链表

引言 链表(Linked List)是数据结构中最基础且最重要的线性存储结构之一。与数组的连续内存分配不同,链表通过指针将分散的内存块串联起来,具有动态扩展和高效插入/删除的特性。本文将以C/C++语言为例,从底层原理到代码实现,手把手教你构建完整的链表结构,并深入探讨其应…

《redis4.0 通信模块源码分析(一)》

【redis导读】redis作为一款高性能的内存数据库&#xff0c;面试服务端开发&#xff0c;redis是绕不开的话题&#xff0c;如果想提升自己的网络编程的水平和技巧&#xff0c;redis这款优秀的开源软件是很值得大家去分析和研究的。 笔者从大学毕业一直有分析redis源码的想法&…

开源安全一站式构建!开启企业开源治理新篇章

在如今信息技术日新月异、飞速发展的数字化时代&#xff0c;开源技术如同一股强劲的东风&#xff0c;为企业创新注入了源源不断的活力&#xff0c;然而&#xff0c;正如一枚硬币有正反两面&#xff0c;开源技术的广泛应用亦伴随着不容忽视的挑战。安全风险如影随形&#xff0c;…

DeePseek结合PS!批量处理图片的方法教程

​ ​ 今天我们来聊聊如何利用deepseek和Photoshop&#xff08;PS&#xff09;实现图片的批量处理。 传统上&#xff0c;批量修改图片尺寸、分辨率等任务往往需要编写脚本或手动处理&#xff0c;而现在有了AI的辅助&#xff0c;我们可以轻松生成PS脚本&#xff0c;实现自动化处…

13.代理模式(Proxy Pattern)

定义 代理模式&#xff08;Proxy Pattern&#xff09; 是一种结构型设计模式&#xff0c;它通过提供一个代理对象来控制对目标对象的访问。代理对象作为客户端与目标对象之间的中介&#xff0c;间接地访问目标对象的功能。代理模式可以在不改变目标对象的情况下增加一些额外的…

DBeaver连接MySQL提示Access denied for user ‘‘@‘ip‘ (using password: YES)的解决方法

在使用DBeaver连接MySQL数据库时&#xff0c;如果遇到“Access denied for user ip (using password: YES)”的错误提示&#xff0c;说明用户认证失败。此问题通常与数据库用户权限、配置错误或网络设置有关。本文将详细介绍解决此问题的步骤。 一、检查用户名和密码 首先&am…