树形数据结构---堆

1.概念

什么是堆?堆和树的区别是什么?它的应用场景有哪些?

堆(Heap)是一种基于树形结构的数据结构,它是一种特殊的完全二叉树。堆的特点是每个节点都满足堆的性质,即父节点的键值总是大于或等于(小于或等于)任何一个子节点的键值。这种性质被称为“堆序性”。堆分为最大堆和最小堆两种类型,其中最大堆的根节点键值是所有节点中最大的,最小堆的根节点键值是所有节点中最小的。

  • 最大堆(Max Heap):父节点的键值大于等于其子节点的键值。
  • 最小堆(Min Heap):父节点的键值小于等于其子节点的键值。

堆和树的区别在于:

  • 高效的插入和删除操作:由于堆是完全二叉树,插入和删除操作的时间复杂度为 ( O(log n) ),其中 ( n ) 是堆中元素的数量。这使得堆在优先级队列等场景中非常有用。
  • 高效的获取最值操作:在最大堆中,最大值位于根节点;在最小堆中,最小值位于根节点。因此,获取最值的操作时间复杂度为 ( O(1) )。

2.堆的实际应用场景

堆在计算机科学中有着广泛的应用场景,其中最常见的应用之一是优先队列。除了优先队列之外,堆还被用于解决各种算法和数据结构中的问题,例如堆排序、最小(大)k个元素、图算法中的最短路径算法(如Dijkstra算法)等。

以下是堆的一些实际应用场景:

1. **优先队列**:堆可用于实现优先队列,其中元素按照优先级进行排序。优先队列常用于任务调度、事件处理等场景,例如操作系统中的进程调度、计算机网络中的路由器转发、模拟系统中的事件处理等。

2. **堆排序**:堆排序是一种高效的排序算法,其时间复杂度为 O(n log n)。堆排序利用堆的性质将数组转换为一个最小堆或最大堆,然后通过不断从堆顶取出元素并调整堆的结构来实现排序。

3. **最小(大)k个元素**:堆可以帮助我们在一个包含大量元素的数据集中快速找到最小或最大的 k 个元素。通过维护一个大小为 k 的堆,我们可以在 O(n log k) 的时间复杂度内找到所需的结果。

4. **Dijkstra算法**:Dijkstra算法是一种用于解决单源最短路径问题的算法,常用于网络路由、地图路线规划等场景。在Dijkstra算法中,堆可以用来高效地选择下一个要处理的节点,从而加速路径计算的过程。

5. **堆在操作系统中的应用**:堆内存管理是操作系统中的一个重要部分,用于动态分配内存给进程。操作系统通常会使用堆来管理进程的动态内存分配,例如通过malloc和free函数在堆中分配和释放内存。

这些只是堆在计算机科学中的一些应用场景,实际上堆还有更多用途,可以根据具体的问题需求来灵活应用。

3.堆的实现与操作

我们了解了什么是堆,那我们如何用代码实现呢

首先我们可以完成堆的push操作,

我们能够看得出来这是一个大堆,这颗像树一样的图是它的逻辑结构,而底下的数组是它的物理结构,也就是说,我们真正操作的是数组。当然,也是可以用指针串联起来的,不过那样操作会很复杂,也没必要。现在,我们想在2的后面插入一个22,在数组中我们可以直接将它放入,但是,这样操作之后,它将不是一个堆。因为它并不满足堆的要求,所以,我们还需要进一步的将数据调整。

具体操作如下:

放入代码又是怎样操作的呢,

/* 向上调整 */
void HeapIfyUp(HPDataType* a, int Child)
{//因为我们的下标是整数,再求父亲节点的时候,不论是左孩子还是右孩子,都可以通过(Child - 1) / 2求出其父亲节点int parent = (Child - 1) / 2;while (Child > 0){/* 大堆,它的父亲节点要大于孩子节点,否则就交换*/if (a[Child] > a[parent]){Swap(&a[parent], &a[Child]);Child = parent;parent = (Child - 1) / 2;}else{break;}}
}void HeapPush(Heap* hp, HPDataType x)
{assert(hp);/* 申请空间 */if (hp->_capacity == hp->_size){size_t newCapacity = hp->_capacity == 0 ? 4 : hp->_capacity * 2;HPDataType* tmp = (HPDataType*)realloc(hp->_a, sizeof(HPDataType) * newCapacity);if (NULL == tmp){perror("HeapPush realloc fail");return;}hp->_a = tmp;hp->_capacity = newCapacity;}hp->_a[hp->_size] = x;hp->_size++;/* 将数据向上调整 */HeapIfyUp(hp->_a, hp->_size - 1);
}

  • 删除堆顶元素
  • 堆一般都是进行删除堆顶元素,因为堆顶不是最大值就是最小值。一般不会删除非堆顶元素,这是堆这种数据结构的特性决定的。
  • 我们先将堆中的最后一个元素赋值给堆顶元素,然后再删除最后一个元素,最后再进行自上而下的堆化操作。

  • 代码实现如下:
    void HeapIfyDown(HPDataType* a, int n, int parent)
    {//算出最大的左右中一个孩子节点(假设最大的为左孩子)int child = (parent * 2) + 1;while (child < n){//根据特性左孩子不存在,则右孩子一定不存在。//如果假设错误,则更新最小孩子节点if (child < n && a[child] > a[child + 1]){child++;}//孩子节点大于父亲节点,交换并重新计算最大孩子节点if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = (parent * 2) + 1;}else{break;}}
    }
    void HeapPop(Heap* hp)
    {assert(hp);if (!HeapEmpty(hp)){//交换堆顶和堆尾数据Swap(&hp->_a[0], &hp->_a[hp->_size - 1]);hp->_size--;HeapIfyDown(hp->_a, hp->_size - 1, 0);}
    }

    4.堆排序

  • 给定一个数组,其中的元素都是无序杂乱的,我们怎么对它进行堆排序呢?
    我们可以通过建堆来进行排序
  • 代码实现如下
    /* 向下调整 */
    void HeapifyDown(HPDataType* a, int sz, int parent)
    {int largest = parent;int left = 2 * parent + 1;int right = 2 * parent + 2;// 比较左孩子和父节点if (left < sz && a[left] < a[largest])largest = left;// 比较右孩子和当前最大值节点if (right < sz && a[right] < a[largest])largest = right;// 如果最大值不是当前节点,交换并递归调整子树if (largest != parent){Swap(&a[parent], &a[largest]);HeapifyDown(a, sz, largest);}
    }// 堆排序函数
    void HeapSort(HPDataType* a, int sz)
    {// 构建最大堆for (int i = sz / 2 - 1; i >= 0; i--) {HeapifyDown(a, sz, i);}// 交换堆顶元素与堆尾元素,并调整堆for (int i = sz - 1; i > 0; i--) {// 交换堆顶元素与堆尾元素Swap(&a[0], &a[i]);// 调整堆HeapifyDown(a, i, 0);}
    }

    5.总代码

  • tree_heap.c
    #include "tree_heap.h"void HeapInit(Heap* hp)
    {hp->_capacity = 0;hp->_size = 0;hp->_a = NULL;
    }void Swap(HPDataType* px, HPDataType* py)
    {HPDataType tmp = *px;*px = *py;*py = tmp;
    }
    /* 向上调整 */
    void HeapIfyUp(HPDataType* a, int Child)
    {//因为我们的下标是整数,再求父亲节点的时候,不论是左孩子还是右孩子,都可以通过(Child - 1) / 2求出其父亲节点int parent = (Child - 1) / 2;while (Child > 0){/* 大堆,它的父亲节点要大于孩子节点,否则就交换*/if (a[Child] > a[parent]){Swap(&a[parent], &a[Child]);Child = parent;parent = (Child - 1) / 2;}else{break;}}
    }void HeapPush(Heap* hp, HPDataType x)
    {assert(hp);/* 申请空间 */if (hp->_capacity == hp->_size){size_t newCapacity = hp->_capacity == 0 ? 4 : hp->_capacity * 2;HPDataType* tmp = (HPDataType*)realloc(hp->_a, sizeof(HPDataType) * newCapacity);if (NULL == tmp){perror("HeapPush realloc fail");return;}hp->_a = tmp;hp->_capacity = newCapacity;}hp->_a[hp->_size] = x;hp->_size++;/* 将数据向上调整 */HeapIfyUp(hp->_a, hp->_size - 1);
    }void HeapDestory(Heap* hp)
    {if (hp->_a){free(hp->_a);hp->_a = NULL;}
    }int HeapEmpty(Heap* hp)
    {assert(hp);return hp->_size == 0;
    }int HeapSize(Heap* hp)
    {assert(hp);return hp->_size;
    }HPDataType HeapTop(Heap* hp)
    {assert(hp);if (!HeapEmpty(hp)){return hp->_a[0];}}
    void HeapIfyDown(HPDataType* a, int n, int parent)
    {//算出最大的左右中一个孩子节点(假设最大的为左孩子)int child = (parent * 2) + 1;while (child < n){//根据特性左孩子不存在,则右孩子一定不存在。//如果假设错误,则更新最小孩子节点if (child < n && a[child] > a[child + 1]){child++;}//孩子节点大于父亲节点,交换并重新计算最大孩子节点if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = (parent * 2) + 1;}else{break;}}
    }
    void HeapPop(Heap* hp)
    {assert(hp);if (!HeapEmpty(hp)){//交换堆顶和堆尾数据Swap(&hp->_a[0], &hp->_a[hp->_size - 1]);hp->_size--;HeapIfyDown(hp->_a, hp->_size - 1, 0);}
    }/* 向下调整 */
    void HeapifyDown(HPDataType* a, int sz, int parent)
    {int largest = parent;int left = 2 * parent + 1;int right = 2 * parent + 2;// 比较左孩子和父节点if (left < sz && a[left] < a[largest])largest = left;// 比较右孩子和当前最大值节点if (right < sz && a[right] < a[largest])largest = right;// 如果最大值不是当前节点,交换并递归调整子树if (largest != parent){Swap(&a[parent], &a[largest]);HeapifyDown(a, sz, largest);}
    }// 堆排序函数
    void HeapSort(HPDataType* a, int sz)
    {// 构建最大堆for (int i = sz / 2 - 1; i >= 0; i--) {HeapifyDown(a, sz, i);}// 交换堆顶元素与堆尾元素,并调整堆for (int i = sz - 1; i > 0; i--) {// 交换堆顶元素与堆尾元素Swap(&a[0], &a[i]);// 调整堆HeapifyDown(a, i, 0);}
    }
    

    tree_heap.h

  • #include <stdio.h>
    #include <string.h>
    #include <malloc.h>
    #include <assert.h>
    #include <stdbool.h>
    typedef int HPDataType;
    typedef struct 
    {HPDataType* _a;int _size;int _capacity;}Heap;// 堆的构建
    void HeapCreate(Heap* hp, HPDataType* a, int n);
    // 堆的销毁
    void HeapDestory(Heap* hp);
    // 堆的插入
    void HeapPush(Heap* hp, HPDataType x);
    void HeapInit(Heap* hp);
    // 堆的删除
    void HeapPop(Heap* hp);
    // 取堆顶的数据
    HPDataType HeapTop(Heap* hp);
    // 堆的数据个数
    int HeapSize(Heap* hp);
    // 堆的判空
    int HeapEmpty(Heap* hp);
    void HeapSort(HPDataType* a, int sz);

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

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

相关文章

8.删除有序数组中的重复项 II

文章目录 题目简介题目解答解法一&#xff1a;双指针&#xff08;快慢指针&#xff09;代码&#xff1a;复杂度分析&#xff1a; 题目链接 大家好&#xff0c;我是晓星航。今天为大家带来的是 删除有序数组中的重复项 II 相关的讲解&#xff01;&#x1f600; 题目简介 题目解…

[NSSRound#1 Basic]sql_by_sql

[NSSRound#1 Basic]sql_by_sql 这题没啥难的&#xff0c;二次注入盲注的套题 先注册&#xff0c;进去有个修改密码 可能是二次注入 修改密码处源码 <!-- update user set password%s where username%s; -->重新注册一个admin-- 获得admin身份&#xff08;原理看sqli-l…

【日常开发之FTP】Windows开启FTP、Java实现FTP文件上传下载

【日常开发之FTP】windows开启FTP、Java实现FTP文件上传下载 FTP前言FTP是什么&#xff1f;FTP两种模式 Windows开启FTPFTP windows 配置防火墙配置 Java部分Maven配置创建FTPClient 注意 FTP前言 FTP是什么&#xff1f; FTP是一个专门进行文件管理的操作服务&#xff0c;一般…

从github上复制代码,记录失败过程,最后放弃挣扎直接去github上手动下载了

从github中复制代码出现一下两种报错 1、Failed to connect to github.com port 443: 连接超时 2、Failed to connect to github.com port 443: 拒绝连接 解决办法如下&#xff1a; https://www.ipaddress.com/ github.com的IP地址为140.82.114.3 终端打开hosts文件加入140…

智慧变电站守护者:TSINGSEE青犀AI视频智能管理系统引领行业革新

一、方案概述 随着科技的不断进步&#xff0c;人工智能&#xff08;AI&#xff09;技术已经深入到各个领域。在变电站安全监控领域&#xff0c;引入AI视频监控智能分析系统&#xff0c;可以实现对站内环境、设备状态的实时监控与智能分析&#xff0c;从而提高变电站的安全运行…

找不到msvcr120.dll无法继续执行

windows&#xff08;新安装的系统&#xff09;安装mysql&#xff0c;报错MSVCR120.dll找不到 官方下载地址 https://www.microsoft.com/zh-CN/download/details.aspx?id40784&wd&eqid9eba4d380059694e00000004658ce260 安装上就好了

Linux —— 信号初识

Linux —— 信号初识 什么是信号测试几个信号signal函数函数原型参数说明返回值注意事项示例 后台程序前台转后台检测输入中断向量表 我们今天来继续学习Linux的内容&#xff0c;今天我们要了解的是Linux操作系统中的信号&#xff1a; 什么是信号 信号是操作系统内核与进程之…

Kubernetes的基本概念

目录 一.基本内容 1.定义 2.作用 二.特性 1.弹性伸缩 2.自我修复 3.服务发现和负载均衡 4.自动发布&#xff08;默认滚动发布模式&#xff09;和回滚 5.集中化配置管理和密钥管理 6.存储编排&#xff0c;支持外挂存储并对外挂存储资源进行编排 7.任务批处理运行 三…

二、使用插件一键安装HybirdCLR

预告 本专栏将介绍如何使用这个支持热更的AR开发插件&#xff0c;快速地开发AR应用。 插件简介 通过热更技术实现动态地加载AR场景&#xff0c;简化了AR开发流程&#xff0c;让用户可更多地关注Unity场景内容的制作。 热更方案 基于HybirdCLR HybridCLR是一个特性完整、零成…

100G ZR4 80KM光模块产品亮点有哪些

之前的文章我们介绍了100G ZR4 80KM光模块的产品特征以及技术原理等&#xff0c;那本期文章我们来了解一下易天第二代100G ZR4 80KM光模块的产品亮点。 首先我们通过下面这张表格以最直观的方式来了解第一代和第二代100G ZR4 80KM光模块在工作温度、功耗、FEC纠错等方面有哪些…

Rust Postgres实例

Rust Postgres介绍 Rust Postgres是一个纯Rust实现的PostgreSQL客户端库&#xff0c;无需依赖任何外部二进制文件2。这意味着它可以轻松集成到你的Rust项目中&#xff0c;提供对PostgreSQL的支持。 特点 高性能&#xff1a;Rust Postgres提供了高性能的数据库交互功能&#…

从心理学角度看,GPT 对人有什么影响?

开启个性化AI体验&#xff1a;深入了解GPT的无限可能 导言 GPT 与我们日常生活的融合标志着技术进步的重大飞跃&#xff0c;为提高效率和创新提供了前所未有的机遇。然而&#xff0c;当我们与这些智能系统日益紧密地交织在一起时&#xff0c;探索它们对个人产生的细微的心理影响…