【数据结构】堆(Heap)详解

news2024/11/16 20:32:29

 在深入了解堆这一重要的数据结构之前,不妨先回顾一下我之前的作品 ——“二叉树详解”。

上篇文章👉剖析二叉树(Binary Tree)

 二叉树作为一种基础的数据结构,为我们理解堆以及其他更复杂的数据结构奠定了坚实的基础。它的结构特点、遍历方式以及在不同场景下的应用,都与堆有着千丝万缕的联系。通过对二叉树的深入学习,能更好地帮助我们掌握堆的相关知识。


目录

一、引言

二、堆的基础概念

(一)完全二叉树与满二叉树

(二)堆的定义与性质

三、堆的操作

(一)堆的插入

(二)堆的删除

(三)最大堆

(四)最小堆

四、堆的应用

(一)堆排序

(二)优先队列

五、总结


一、引言

堆是计算机科学中一种非常重要的数据结构,它在众多算法和应用中发挥着关键作用。本文将深入探讨堆的相关概念、操作及其应用。

二、堆的基础概念

(一)完全二叉树与满二叉树

       1.完全二叉树

  • 若二叉树的深度为 h,则除第 h 层外,其他层的结点全部达到最大值,且第 h 层的所有结点都集中在左子树。 

        2.满二叉树

  • 满二叉树是一种特殊的完全二叉树,所有层的结点都是最大值

 

(二)堆的定义与性质

        1.堆的定义

  • 堆(英语:heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。
  • n 个元素的序列 \left \{ k_{1},k_{2},k_{i},\cdot\cdot\cdot ,k_{n}\right \} 当且仅当满足下关系时,称之为堆:
  • 堆分为大顶堆小顶堆。在大顶堆中,父节点的值总是大于或等于子节点的值;在小顶堆中,父节点的值总是小于或等于子节点的值

         2.堆的性质

  • 堆中某个节点的值总是不大于或不小于其父节点的值。

  • 堆总是一棵完全二叉树。

  • 将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。

  • 注意:在二叉树中,若当前节点的下标为,则其父节点的下标为i/2,其左子节点的下标为i*2,其右子节点的下标为i*2+1

三、堆的操作

(一)堆的插入

        1.插入过程

  • 每次插入都是将先将新数据放在数组最后。由于从这个新数据的父结点到根结点必然为一个有序的序列,现在的任务是将这个新数据插入到这个有序序列中,类似于直接插入排序中将一个数据并入到有序区间中。
  • 例如,将数字16插入到堆中,堆的数组是\left [ 10,7,2,5,1\right ]
    • 第一步是将新的元素插入到数组的尾部,数组变成,相应的树变成了:\left [ 10,7,2,5,1,16 \right ]
       10
       7  2
       5 1 16
  • 此时堆属性不满足,因为2在的16上面(这是一个最大堆),需要交换和。得到:
       10
       7 16
       5 1 2
  • 现在还未完成,因为 10 也比 16 小。继续交换插入元素和它的父节点,直到父节点比它大或者到达树的顶部。这就是所谓的 shift-up,每一次插入操作后都需要进行,它将一个太大或者太小的数字 “浮起” 到树的顶部。
  • 最后得到的堆:
       16
       7 10
       5 1 2
  • 插入操作的 C 语言代码示例
void insertHeap(int heap[], int *size, int value) {
    heap[*size] = value;
    int index = *size;//将孩子标为index
    (*size)++;
    while (index > 0) //将孩子调整到正确位置
    {
        int parentIndex = (index - 1) / 2;
        if (heap[index] > heap[parentIndex]) //如果孩子比父亲大,则交换
        {
            int temp = heap[index];
            heap[index] = heap[parentIndex];
            heap[parentIndex] = temp;
            index = parentIndex;//再次与其父亲比较直到break
        }
         else break;//孩子比父亲小则退出
    }
}

(二)堆的删除

        1.删除过程

  • 堆中每次都只能删除堆顶元素。为了便于重建堆,实际的操作是1.将最后一个数据的值赋给根结点,2.然后再从根结点开始进行一次从上向下的调整。3.调整时先在左右子结点中找最小的(对于最大堆),如果父结点比这个最小的子结点还小说明不需要调整了,反之将父结点和它交换后再考虑后面的结点。相当于根结点数据的 “下沉” 过程。
  • 例如,删除树中的10
           16
           7 10
           5 1 2
    • 顶部有一个空的节点,取出数组中的最后一个元素 1,将它放到树的顶部。
    • 然后进行 shift-down 操作。为了保持最大堆的堆属性,需要树的顶部是最大的数据。现在有两个数字 7 和 2 可用于交换,选择两者中的较大者7放在树的顶部, 7 交换和 1 ,树变成了:
       7
       1  2
       5
  • 继续堆化直到该节点没有任何子节点或者它比两个子节点都要大为止。对于这个堆,只需要再有一次交换就恢复了堆属性:
       7
       5  2
  • 删除操作的 C 语言代码示例
int deleteHeap(int heap[], int *size) {
    if (*size == 0) {
        return -1;
    }
    int value = heap[0];
    heap[0] = heap[*size - 1];
    (*size)--;
    int index = 0;
    while (1) {
        int leftChildIndex = 2 * index + 1;
        int rightChildIndex = 2 * index + 2;
        int largestIndex = index;
        if (leftChildIndex < *size && heap[leftChildIndex] > heap[largestIndex]) {
            largestIndex = leftChildIndex;
        }
        if (rightChildIndex < *size && heap[rightChildIndex] > heap[largestIndex]) {
            largestIndex = rightChildIndex;
        }
        if (largestIndex!= index) {
            int temp = heap[index];
            heap[index] = heap[largestIndex];
            heap[largestIndex] = temp;
            index = largestIndex;
        } else {
            break;
        }
    }
    return value;
}

(三)最大堆

 1.构造最大堆

  • 基本思想:首先将每个叶子节点视为一个堆,再将每个叶子节点与其父节点一起构造成一个包含更多节点的堆。所以,在构造堆的时候,首先需要找到最后一个节点的父节点,从这个节点开始构造最大堆;直到该节点前面所有分支节点都处理完毕,这样最大堆就构造完毕了。
  • 假设树的节点个数为n,以1为下标开始编号,直到结束。对于节点 i ,其父节点为i/2;左孩子节点为i*2,右孩子节点为i*2+1。最后一个节点的下标为n,其父节点的下标为n/2
  • 例如,原始数据为a\left [ \right ]=\left \{ 4,1,3,2,16,9,10,14,8,7 \right \},采用顺序存储方式,对应的完全二叉树构建最大堆的过程如下:
    • 首先,最后一个节点为7,其父节点为16,从16这个节点开始构造最大堆。
    • 构造完毕之后,转移到下一个父节点2,直到所有父节点都构造完毕。
    • 具体过程如下图所示(括号内为每次调整后的结果)
       4
       1 3 (3 1)
       2 16 9 10 (14 16 9 10)
       14 8 7 (2 8 7)
       (a)       (b)
       4
       1 10 (10 1)
       14 16 9 3 (14 16 9 3)
       2 8 7 (2 8 7)
       (c)       (d)
       16
       14 10
       8 7 9 3
       2 4 1
       (e) 堆的初始化步骤
  • 代码实现
struct MaxHeap {
    Etype *heap; //数据元素存放的空间,下标从1开始存数数据,下标为0的作为工作空间,存储临时数据
    int HeapSize; //数据元素的个数
    int MaxSize; //存放数据元素空间的大小
};
MaxHeap H;

void MaxHeapInit(MaxHeap &H) {
    for (int i = H.HeapSize / 2; i >= 1; i--) {
        H.heap[0] = H.heap[i];
        int son = i * 2;
        while (son <= H.HeapSize) {
            if (son < H.HeapSize && H.heap[son] < H.heap[son + 1])
                son++;
            if (H.heap[0] >= H.heap[son])
                break;
            else {
                H.heap[son / 2] = H.heap[son];
                son *= 2;
            }
        }
        H.heap[son / 2] = H.heap[0];
    }
}

2.最大堆插入节点

  • 思想:先在堆的最后添加一个节点,然后沿着堆树上升。跟最大堆的初始化过程大致相同。

代码实现: 

void MaxHeapInsert(MaxHeap &H, EType &x) {
    if (H.HeapSize == H.MaxSize)
        return false;
    int i = ++H.HeapSize;
    while (i!= 1 && x > H.heap[i / 2]) {
        H.heap[i] = H.heap[i / 2];
        i = i / 2;
    }
    H.heap[i] = x;
    return true;
}

3.最大堆堆顶节点删除

  • 思想:将堆树的最后的节点提到根结点,然后删除最大值,然后再把新的根节点放到合适的位置。

代码实现: 

void MaxHeapDelete(MaxHeap &H, EType &x) {
    if (H.HeapSize == 0)
        return false;
    x = H.heap[1];
    H.heap[0] = H.heap[H.HeapSize--];
    int i = 1, son = i * 2;

    while (son <= H.HeapSize) {
        if (son <= H.HeapSize && H.heap[0] < H.heap[son + 1])
            son++;
        if (H.heap[0] >= H.heap[son])
            break;
        H.heap[i] = H.heap[son];
        i = son;
        son = son * 2;
    }
    H.heap[i] = H.heap[0];
    return true;
}

(四)最小堆

整体操作和最大堆类似,主要区别在于比较大小的规则相反。在最小堆中,每个节点的值都不大于其子节点的值。插入和删除操作时,调整堆的方向也与最大堆相反,以保持最小堆的性质。

四、堆的应用

(一)堆排序

1.原理

堆排序利用了堆(通常是最大堆或最小堆)的特性来实现排序。对于最大堆,每次可以取出堆顶元素(即当前堆中的最大值),然后将堆的最后一个元素放到堆顶,再进行调整使堆保持最大堆性质。重复这个过程,就可以得到一个有序的序列。

2.示例

例如,对数组进行堆排序。首先根据该数组元素构建一个完全二叉树,具体过程如下(从左到右,从上到下按顺序一步一步的详细过程):

  • 初始完全二叉树:
       16
       7
       3
       20
       17
       8
  • 构建最大堆过程:
       16 16 20
       7 8 20 8 16 8
       20 17 3 7
       20
       17
       8
       7
       16
       3
       3
       17 8
       16 20
       17
       3 8
       7 16 20
       17 3 16
       16 8 16 8 3 8
       7 3 7 20 7
       16 3 8
       7 8 7 8 7 3
       3 20 16
       3 7 3
       7 8 3 9 8
       1 17 20 16 16 
  • 然后进行堆排序,每次取出堆顶元素(最大值),与数组末尾元素交换,再对剩余元素进行调整,重复这个过程,最终得到有序数组。

  • 相关参考文章:我的十大经典排序算法 (C++、Java 实现) 动图演示及代码解析 - 冒泡排序、选择排序、插入排序、快速排序、堆排序、希尔排序、归并排序、计数排序、桶排序、基数排序

        有各个经典排序算法的动图介绍和源码,可以参考。 

(二)优先队列

堆可以用于实现优先队列。在优先队列中,元素按照优先级进行排序。最大堆可以用于实现最大优先队列,每次取出的元素是优先级最高(值最大)的元素;最小堆可以用于实现最小优先队列,每次取出的元素是优先级最高(值最小)的元素。插入和删除元素时,堆的操作可以保证优先队列的性质始终满足。

五、总结

是一种非常重要的数据结构,具有独特的性质和高效的操作。它在堆排序、优先队列等算法和应用中有着广泛的应用。通过对堆的深入理解和掌握,我们可以更好地设计和实现高效的算法,解决各种实际问题。在未来的计算机科学领域,堆的应用可能会随着技术的发展而更加广泛,例如在大数据处理、实时系统等方面发挥更大的作用。同时,对于堆的研究和优化也将不断深入,以提高其性能和适用性。希望大家在学习堆的过程中,能充分体会到它的魅力和价值。

如果在阅读过程中有任何疑问或建议,欢迎随时交流。


💝💝💝感谢你看到最后,点个赞再走吧!💝💝💝 

以下是一个投票,欢迎你参与: 

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

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

相关文章

Python 从入门到实战30(高级文件的操作)

我们的目标是&#xff1a;通过这一套资料学习下来&#xff0c;通过熟练掌握python基础&#xff0c;然后结合经典实例、实践相结合&#xff0c;使我们完全掌握python&#xff0c;并做到独立完成项目开发的能力。 上篇文章我们讨论了操作目录的相关知识。今天我们将学习一下高级文…

distinct导致sql超时

前言 昨天敲着敲着代码&#xff0c;小杨哥跑过来给我说&#xff0c;快看他们大会议室演示报错了&#xff0c;还是一堆错了。完了啊在演示的时候报错&#xff01;&#xff01;&#xff01;接下来我们分析一下是什么原因吧。 问题分析 查看日志&#xff1a; 从日志打印看明显的…

[大语言模型-论文精读] 大语言模型是单样本URL分类器和解释器

[大语言模型-论文精读] 大语言模型是单样本URL分类器和解释器 目录 文章目录 [大语言模型-论文精读] 大语言模型是单样本URL分类器和解释器目录1. 论文信息2. 摘要3. 引言4. 相关工作A. 网络钓鱼URL检测B. 使用LLMs进行单样本分类 C. LLMs作为分类器的可解释性 5. 论文所提框架…

自媒体人打造视频号爆款短视频其实很简单

最近找我做自媒体起号辅导的非常多&#xff0c;当然今天的方法也很适合我们的自媒体短视频运营。 美国心理学家米勒(Miller)被誉为认知心理学奠基人之一&#xff0c;有关短时记忆容量的研究表明&#xff1a;人的短时记忆保持时间在无复述的情况下只有5~20秒&#xff0c;最长也…

Linux命令:用于处理 XML 文档的强大的命令行工具xmlstarlet 详解

目录 一、概述 二、功能特点 1、查询和过滤 2、修改和更新 3、批处理操作 4、跨平台支持 5、转换和格式化 6、验证和校验 三、 安装 xmlstarlet 四、 基本用法 1、xmlstarlet 的基本语法 2、获取帮助 五、 常用命令 1. ed&#xff08;编辑&#xff09; - 用于编辑…

大型模型智能体:最先进的合作范式、安全与隐私以及未来趋势

摘要—大型模型智能体&#xff08;LM agents&#xff09;&#xff0c;由如 GPT-4 和 DALL-E 2 等大型基础模型驱动&#xff0c;代表了实现人工通用智能&#xff08;AGI&#xff09;的重要一步。LM 智能体展示了自主性、具身性和连接性等关键特征&#xff0c;使其能够在物理、虚…

java在开发中的总结

1.异步执行数据遍历 public static void main(String[] args) {List<Integer> numbers Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);numbers.parallelStream().forEach(i->{System.out.println(i);});}在工作中&#xff0c;我们可以对for循环进行改进&#xff0c;…

C++11:现代C++的演变与提升

目录 前言 一、统一的列表初始化 1、{}初始化 2、std::initializer_list 二、新的声明 1、auto 2、decltype 3、nullptr 三、范围for循环 四、右值引用与移动语义 1. 左值 vs 右值 2、移动构造与移动赋值 3、 move转换 4、完美转发&#xff1a;forward 五、lamb…

调和级数枚举+前缀和,CF 731F - Video Cards

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 731F - Video Cards 二、解题报告 1、思路分析 题目提示的很明显要用调和…

复杂类型 el-form 表单的校验

背景描述 表单结构 form 表单结构 活动名称 - 必填&#xff0c;prop“name”活动类型 - 必填&#xff0c;prop“actType”活动人数 - 必填&#xff0c;prop“actUserAccount”签到记录 - 必填&#xff0c;prop“actList” 对表单做必填校验 rulesOld: {name: [{ required: t…

【15%】100小时机器学习——什么是机器学习

前言 虽然已经好久没有更新了&#xff0c;但笔者最近一直都在努力学习哦。 前面三三两两根据GitHub上的项目写了一些实验操作&#xff0c;但是总觉得这样是不行的。碎片化的学习只能是建立在已知的基础上进行熟练&#xff0c;不能作为打基础的主力方法&#xff0c;最关键的是&a…

物联网系统中声音拾取音频方案_咪头

01 物联网系统中为什么要使用咪头 物联网系统中使用咪头&#xff08;麦克风或传声器&#xff09;的原因主要可以归结为以下几个方面&#xff1a; 声音信号的拾取与转换 基本功能&#xff1a;咪头是一种将声音转换为电信号的装置。在物联网系统中&#xff0c;咪头负责捕捉周围…

一键降重:芝士AI如何简化论文查重过程?

大家写论文时“旁征博引”是常规操作&#xff0c;所以重复率就成了投稿前的“噩梦”。自己降重&#xff0c;发现怎么改写都无法下降重复率&#xff0c;可能一天改下来下降3%&#xff0c;让人抓狂。 但今天开始&#xff0c;你不用再苦恼啦&#xff0c;更不用自己抓耳挠腮一整天…

商汤SenseNova 5.5大模型的应用实践

SenseNova 5.5如何重塑金融、医疗与自动驾驶的未来 ©作者|wy 来源|神州问学 一、引言 人工智能&#xff08;AI&#xff09;作为引领未来发展的重要力量&#xff0c;正以前所未有的速度改变着我们的生活和工作方式。每年一度的世界人工智能大会&#xff08;WAIC&#xf…

高密度EEG人脑成像:技术与应用

摘要 EEG是一种非侵入性的人脑神经活动测量技术。随着数字技术的进步&#xff0c;EEG分析已从定性分析幅值和频率调制发展到全面分析记录信号的复杂时空特征。EEG能够在亚秒级的时间范围内测量神经过程&#xff0c;但其空间分辨率较低&#xff0c;这使得难以准确可靠地定位EEG…

【Gitee自动化测试5】Gitee免费版的所有按钮

一、首页 就红框里有用 1. 仓库&#xff08;Repository&#xff09; 功能&#xff1a;仓库是一个项目的存储空间&#xff0c;用于保存源代码、文档、配置文件等与项目相关的内容。每个仓库通常会有一个版本控制系统&#xff08;如 Git&#xff09;来跟踪代码的变更历史。用…

Xinstall助力广告主实现精准投放,提升App广告效果!

随着移动互联网的快速发展&#xff0c;App广告投放已成为品牌推广的重要手段。然而&#xff0c;广告投放的效果如何&#xff0c;是否达到了预期的目标&#xff0c;这些问题一直困扰着广告主。今天&#xff0c;我们就来聊聊App广告投放数据统计的痛点&#xff0c;以及Xinstall如…

从体质入手:气虚痰湿人群的健康攻略

“气虚”与“痰湿”是中医体质学九大体质中常见的两种易胖体质&#xff0c;可以说大多数肥胖人群都有这两种体质的身影。比如气虚质的特征是容易疲劳&#xff0c;乏力&#xff0c;出汗&#xff0c;抵抗力差&#xff0c;声弱&#xff0c;气短&#xff0c;面部苍白或萎黄等&#…

车辆目标检测、工程车辆检测算法、工程车辆类型检测

工程车辆检测算法主要用于智能交通系统、建筑工地管理、矿山开采、物流运输等领域&#xff0c;通过图像识别技术来检测和识别视频或图像中的工程车辆。这种技术可以帮助管理者实时监控工程车辆的活动&#xff0c;确保施工安全、交通流量管理和资源调度的效率。以下是关于工程车…

cups-browsed远程代码执行漏洞安全风险通告

今日&#xff0c;亚信安全CERT监控到安全社区研究人员发布安全通告&#xff0c;披露了cups-browsed 远程代码执行漏洞(CVE-2024-47176)。由于cups-browsed 服务在处理网络打印任务时&#xff0c;会绑定到 UDP 端口 631 上的 INADDR_ANY 地址&#xff0c;从而信任来自任何来源的…