【数据结构和算法】---二叉树(2)--堆的实现和应用

目录

  • 一、堆的概念及结构
  • 二、堆结构的实现
    • 2.1堆向下调整算法
    • 2.2堆向上调整算法
    • 2.3删除堆顶元素
    • 2.4插入元素
    • 2.5其他函数接口
  • 三、堆结构的应用
    • 3.1堆排序
    • 3.2Top-k问题
  • 四、堆概念及结构相关题目

一、堆的概念及结构

如果有一个数字集合,并把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,且在逻辑结构(即二叉树)中,如果每个父亲节点都大于它的孩子节点那么此堆可以称为大堆;那么如果每个父亲节点都小于它的孩子节点那么此堆可以称为小堆
堆的性质

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树。

关于大/小堆的逻辑结构和存储结构如下:

在这里插入图片描述

由上图我们也可以观察出,虽然在大堆的逻辑结构中,每个父亲节点都要大于它的孩子节点,但在大堆的存储结构中并不是以完全的从大到小的顺序存储的,小堆亦然。

二、堆结构的实现

上面讲述了堆的存储结构结构为数组,那么我们可以像建顺序表那样来建堆,用int capacity来表示堆可存储的数据个数,int size表示当前已存储的数据个数·,HPDataType* a表示存储堆数据的数组。

typedef int HPDataType;typedef struct Heap
{HPDataType* a;int capacity;//数组容量int size;//当前数据个数
}HP;

虽然堆的本质上是一个数组,但我们实现插入和删除操作时,是将其当作一个二叉树来调整的。对于如何标识逻辑结构下的堆的每个节点,因为已知根节点是数组中下标为0的元素,那么用各个节点所对应数组中元素的下标来标识节点(即将完全二叉树结构自第一层次向下依次遍历每一层次节点并计数)。基于此,用parent表示父亲节点,child表示其孩子节点,可以得到如下表达式parent = (child - 1) / 2;child1(左) = parent * 2 + 1;child2(右) = parent * 2 + 2
下面各个函数是以建小堆为目的实现的。

2.1堆向下调整算法

能运用向下调整算法AdjustDown()的前提是,除根节点以外其余都以满足小堆的条件(即父亲节点小于各个孩子节点)。此函数需要三个参数a表示需要调整的数组(堆),parent表示需要调整的那个节点的下标,size表示数组长度。
首先我们要找到此父亲节点的孩子节点,并假设左孩子小于右孩子(child = parent * 2 + 1)。然后比较左右孩子节点大小,取较小的那个作为新的孩子,还需要注意的是我们要新增判断(child + 1 < size)防止没有右孩子导致越界访问,最后比较父亲和孩子节点的大小,并更新父亲和孩子,直至child超出size范围(即再无孩子节点)。
逻辑大致如下:

在这里插入图片描述

//向下调整算法
void AdjustDown(HPDataType* a, int size, int parent)
{//假设判断int child = parent * 2 + 1;//调整if (child + 1 < size && a[child] > a[child + 1]){++child;}//交换while (child < size){if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}

2.2堆向上调整算法

同理,能运用向上调整算法AdjustUp()的前提是,除要插入节点的位置(即下标为size)以外其余都以满足小堆的条件(即父亲节点小于各个孩子节点)。与向下调整算法不同的是,AdustUp()需要两个参数,一个为a表示需要调整的数组(堆),另一个为child表示所需调整节点的下标(即数组最后一个元素)。
与向下调整算法不同的是,向上调整不需要比较两个孩子的大小,因为其余节点已满足父亲节点大于孩子节点。于是乎,首先我们要找到父亲节点parent = (child - 1) / 2然后比较父亲和孩子大小,若a[child] < a[parent]就交换,直到child值小于0或父亲节点大于孩子节点结束。
逻辑大致如下:

在这里插入图片描述

//堆向上调整
void AdjustUp(HPDataType* a, int child)
{int parent = (child - 1) / 2;//交换while (child > 0){if (a[child] < a[parent]){Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}

2.3删除堆顶元素

在堆结构中进行删除操作,一般会选择删除堆顶元素,但首先还要确保堆中有节点(断言assert(php->size > 0);),然后可以将最后一个元素(即下标为size - 1),移动到堆顶,并将size--,再进行向下调整算法
逻辑大致如下:

在这里插入图片描述

//删除堆顶--根节点
void HeapPop(HP* php)
{assert(php);assert(php->size > 0);//首尾互换Swap(&php->a[0], &php->a[php->size - 1]);php->size--;//向下调整AdjustDown(php->a, php->size , 0);
}

2.4插入元素

在堆结构中进行插入操作,一般会选择堆尾。因为堆的底层是用数组实现的,且是需要动态开辟的。那么在每次插入元素之前都要先判断一下数组容量capacity,若size == capacity就需要扩容。最后只需要在完成插入操作后,对最后一个元素进行向上调整即可
逻辑大致如下:

在这里插入图片描述

//插入元素
void HeapPush(HP* php, HPDataType x)
{assert(php);//判断容量if (php->size == php->capacity){size_t newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);if (tmp == NULL){perror("HeapPush()::realloc");return;}php->a = tmp;php->capacity = newcapacity;}//插入php->a[php->size] = x;php->size++;//堆向上调整AdjustUp(php->a, php->size - 1);
}

2.5其他函数接口

  1. 判断堆是否为空:php->size就代表堆中的有效的元素个数,那么当php->size == 0为真时就代表堆为空。
//为空判断
bool HeapEmpty(HP* php)
{assert(php);return php->size == 0;
}
  1. 返回堆顶元素:首先就要判断堆中是否有元素(断言即可assert(php->size != 0)),若有则返回数组中下标为0的元素(即堆顶,根节点)。
//返回堆顶
HPDataType HeapTop(HP* php)
{assert(php);assert(php->size != 0);return php->a[0];
}

三、堆结构的应用

了解了堆结构的实现方法,我们便可以将其运用到以下两个问题中:

3.1堆排序

这里的堆排序是基于数组,运用二叉树的性质(即将待排序的数组当作一棵完全二叉树)来实现的,不会过多的动态开辟空间。 要与重新建堆的堆排序区别开(下面topk问题会用到,所以这里就不介绍了)!
如果我们要将此数组排成一个升序的数组,要如何实现呢?
既然此数组可当作一棵完全二叉树,那么首先我们就要将此树排成大堆,那么要建大堆而不建小堆呢?根据堆的性质,大堆的根节点可以筛选最大值,同理 小堆的根节点可以用来筛选最小值,那么如果我们建了小堆,就要 将最小值(即根节点)保留,然后将除此元素的数组的逻辑结构重新当作一个完全二叉树,那么这个二叉树的 各个节点间的关系就全都乱了,需要重新排成小堆

在这里插入图片描述

由以上逻辑我们也可以看出,如果建小堆,那么此问题将变得十分复杂,且时间复杂度也很高。 既然这样,那么我们就可以建大堆来将数组排为升序:

在这里插入图片描述

我们用大堆找到最大值,然后将首尾元素互换,这样大堆的各个节点的关系就不会被打乱(不需要重新排大堆),最后只需要将堆顶的元素向下调整AdjustDown()重新找到次大值,需要注意的是调整时要将size-- 以避免已有最大值对此次调整造成影响,以此类推便得到一个升序数组。


那么我们要如何在一个数组上将其排为大堆呢?介绍以下两种方法:

  1. 方法一:向下调整
    给定一个数组,从下标为(len - 1 - 1) / 2的元素开始,直到下标为0,并将此值赋给parent。对下标为parentlen - 1之间的元素排大堆。(从后面元素开始向下调整)逻辑大致如下:

在这里插入图片描述

  1. 方法二:向上调整
    与向下调整相似,我们可以从下标为1的元素开始,直到下标为len - 1,并将此值赋给child。对下标为0child之间的元素排大堆。(从前面元素开始向上调整)逻辑大致如下:

在这里插入图片描述

那么两种方法谁更优呢?事实上方法一要优于方法二,这里就不多介绍了,只提供一下思路:方法一中我们所需要调整的节点个数相较于数组长度少一半(即少了二叉树最后一层次的调整),且越靠后的层次(节点数多)所需调整的步数越少;而方法二中我们所需要调整的节点个数与数组长度相近,且越靠后的层次(节点数多)所需要调整的步数越多
那么虽然两种方法时间复杂度都为O(N*log(N)),但实际上方法一中调整次数要少于方法二

// 对数组进行堆排序--从小到大
void HeapSort(int* a, int len)
{//方法二:--O(N*logN)/*for (int i = 1; i < len - 1; i++){AdjustUp(a, i);}*///方法一:--O(N*logN)for (int i = (len - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, len - 1, i);}int end = len - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustDown(a, end-1, 0);end--;}
}

3.2Top-k问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。 比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

  1. 用数据集合中前K个元素来建堆
    前k个最大的元素,则建小堆
    前k个最小的元素,则建大堆
  2. 用剩余的N-K个元素依次与堆顶元素来比较,满足则替换堆顶元素,并向下调整

为了模拟此问题,我们可以先造10000个整型放到文件中,要找最大值时再从文件中一个个读出。为了保证数据的随机性,我们可以使用srand()函数,并设置一个不断变化的时间戳(unsigned int)time(0) 具体造数据方法如下:

//造数据
void CreatData()
{int n = 10000;srand((unsigned int)time(0));const char* fileName = "data.txt";FILE* file = fopen(fileName, "w");if (file == NULL){printf("fopen()::fail");exit(-1);}for(size_t i = 0; i < n; i++){int x = (rand() + i) % 10000;fprintf(file, "%d\n", x);}
}

既然此问题的目的是找出最大的k个数(k远小于数据个数),那么我们可以建一个只能存放k个数据的小堆。 估计会有以下两个疑问:

  1. 为什么只建能存放k个数据的堆?
    因为如果将文件中的所以数据都建成堆,那么当数据一多时,动态开辟内存将十分巨大,甚至会造成溢出问题。 且有一个数据插入时,堆都需要重新调整,这样一来时间复杂度将会很高,运行效率也大大降低。
  2. 为什么建小堆而不建大堆?
    反过来想一下,如果建大堆的话,当最大的数已找到,那么它将一直堵在堆顶,其余的所有数都无法进堆。所以我们选择建小堆,堆顶元素最小,每当有新元素时只需要和堆顶进行比较即可,大的替换堆顶并向下调整,小的直接跳过即可

代码实现如下:

//前k个大的数
void PrintTopK(int k)
{//建有五个数的堆--小堆FILE* file = fopen("data.txt", "r");if (file == NULL){printf("fopen()::fail");exit(-1);}int* minheap = (int*)malloc(sizeof(int) * k);if (minheap == NULL){perror("malloc()::fail");exit(-1);}for (int i = 0; i < k; i++){fscanf(file, "%d", &minheap[i]);AdjustUp(minheap, i);}//将数据依次比较,大的下沉--向下调整int x;while (fscanf(file, "%d", &x) != EOF){if (x > minheap[0]){minheap[0] = x;AdjustDown(minheap, k, 0);}}//打印堆for (int i = 0; i < k; i++){printf("%d ", minheap[i]);}putchar('\n');//销毁堆free(minheap);minheap = NULL;
}

四、堆概念及结构相关题目

  1. 已知小根堆为8,15,10,21,34,16,12,删除关键字 8之后需重建堆,在此过程中,关键字之间的比较次
    数是(C)。
    A 1
    B 2
    C 3
    D 4

解: 由此结构可以推断出,逻辑结构的二叉树有三层,将12移动到堆顶,然后向下调整,在调整过程中首先比较两个孩子节点找出较小的那个(第一次),然后比较孩子和父亲节点大小(第两次),因为满足条件所以交换(8来到右子树),因为此时并无右孩子所以省略左右孩子大小的比较,最后只需要比较一次孩子和父亲节点即可(第三次)。

  1. 最小堆[0,3,2,5,7,4,6,8],在删除堆顶元素0之后,其结果是(C
    A[3,2,5,7,4,6,8]
    B[2,3,5,7,4,6,8]
    C[2,3,4,5,7,8,6]
    D[2,3,4,5,6,7,8]

解: 首尾互换,堆顶向下调整

  1. 下列关于向下调整算法的说法正确的是(B
    A.构建堆的时候要对每个结点都执行一次
    B.删除操作时要执行一次
    C.插入操作时要执行一次
    D.以上说法都不正确

解: A: 建堆时,从每一个非叶子节点开始,倒着一直到根节点,都要执行一次向下调整算法。
B: 删除元素时,首先交换堆顶元素与堆中最后一个元素,对中有效元素个数减1,即删除了堆中最后一个元素,最后将堆顶元素向下调整
C: 插入操作需要执行向上调整算法。

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

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

相关文章

MS6459字符叠加可兼容MAX7456

MS6459 是 OSD 电路,与 MCU 配合使用,可控制不同类型的显示系统。显示点阵为 1218&#xff0c;内置部分数字、英文字符、拉丁字符和中文字符。电路集成有上电复位电路和 VRAM 清零电路用于减轻 MCU 的工作量。 主要特点 输入信号&#xff1a;混合视频信号&#xff1b; 显示字符…

ksuser.dll文件缺失怎么办?软件或游戏无法启动,一键自动修复

很多小伙伴反馈&#xff0c;自己的电脑中了病毒&#xff0c;被杀毒软件清理后&#xff0c;在打开游戏或软件的时候&#xff0c;经常会报错“提示无法找到ksuser.dll文件&#xff0c;建议重新安装软件或游戏”。自己根据提示重装后&#xff0c;还是报错&#xff0c;不知道应该怎…

C++ Qt开发:SqlRelationalTable关联表组件

Qt 是一个跨平台C图形界面开发库&#xff0c;利用Qt可以快速开发跨平台窗体应用程序&#xff0c;在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置&#xff0c;实现图形化开发极大的方便了开发效率&#xff0c;本章将重点介绍SqlRelationalTable关联表组件的常用方法及灵…

【C语言】程序练习(四)

大家好&#xff0c;这里是争做图书馆扫地僧的小白。非常感谢各位的支持&#xff0c;也期待着您的关注。 目前博主有着C语言、C、linux以及数据结构的专栏&#xff0c;内容正在逐步的更新。 希望对各位朋友有所帮助同时也期望可以得到各位的支持&#xff0c;有任何问题欢迎私信与…

智能分析网关V4+太阳能供电模式,搭建鱼塘养殖远程视频监控方案

一、行业背景 传统的鱼塘养殖模式由于养殖区域面积大、管理难度高&#xff0c;经常会出现偷钓者、盗窃鱼苗、非法入侵等监管难题&#xff0c;给养殖户带来了不小的经济损失。为了解决这些问题&#xff0c;搭建鱼塘远程监控系统成为了必要之举。通过远程监控系统&#xff0c;管…

别一言不合就重装系统!Windows 无法正常启动先试试这些办法

你是否遇到过在升级或安装 Windows 10 操作系统&#xff0c;Windows 无法正常启动进入桌面&#xff0c;甚至陷入无限循环。造成的原因有很多&#xff0c;比如 Windows 更新&#xff0c;安装了新的软件或者驱动程序&#xff0c;系统文件损坏等等。那遇见 Windows 启动不了怎么办…

【Midjourney】Midjourney根据prompt提示词生成人物图片

目录 &#x1f347;&#x1f347;Midjourney是什么&#xff1f; &#x1f349;&#x1f349;Midjourney怎么用&#xff1f; &#x1f514;&#x1f514;Midjourney提示词格式 Midjourney生成任务示例 例1——航空客舱与乘客 prompt prompt翻译 生成效果 大图展示 细节大…

铂炭催化剂,2026年市场预计将以6.5%左右的复合年增长率增长

铂碳催化剂广泛用于各种工业应用&#xff0c;包括化学、制药和汽车领域。在对清洁能源的需求不断增加和环境问题意识不断提高的推动下&#xff0c;铂碳催化剂市场正在稳步增长。本次分析&#xff0c;我们将从全球市场和中国市场分别考察铂碳催化剂市场的发展趋势。 全球市场分析…

【并发编程篇】线程安全问题_—_ConcurrentHashMap

文章目录 &#x1f354;情景引入&#x1f339;报错了&#xff0c;解决方案 &#x1f354;情景引入 我们运行下面的代码 package org.example.unsafe;import java.util.HashMap; import java.util.Map; import java.util.UUID;public class MapTest {public static void main(…

Kerberos安装教程与命令详解(超详细)

文章目录 前言一、安装准备1. 搭建集群2. 软件包介绍 二、使用shell脚本一键安装1. 复制脚本2. 增加执行权限3. 执行脚本 三、kdb5_util命令1. 简介2. 可用选项和命令的解释3. 常见命令及其说明4. 示例用法 四、kadmin命令1. 简介2. 可用选项和命令的解释3. 常见命令及其说明4.…

芋道视频199 - 工作流 - 关系图 - ruoyi-vue-pro

一 新建表单 数据库&#xff1a;bpm_form。实体类&#xff1a;BpmFormDO.java&#xff1a; 二 流程模型、流程部署、流程定义 1 第1步&#xff1a;创建流程模型 页面操作&#xff1a;实体类&#xff1a;Model.java。数据库&#xff1a;ACT_RE_MODEL 流程模板信息表&#xf…

Python基础进阶:9个易错知识点

你好&#xff0c;我是kelly。 kelly根据自己平时工作&#xff0c;总结9个易错知识点&#xff0c;希望对大家有用。 知识点1&#xff1a;is 和 is比较是两个变量地址是否相同&#xff0c;比较是两个变量的值&#xff08;内容&#xff09;是否相同。 示例&#xff1a; In [92…

2分钟明白什么是SCADA?

SCADA——数据采集和监控系统的英文缩写&#xff0c;国内流行的叫法是&#xff0c;监控组态软件&#xff0c;是生产控制的核心&#xff0c;是位于控制设备之上&#xff0c;侧重于管理的纯软件&#xff0c;在ERP/MES/PCS架构中起承上启下的作用。SCADA负责控制或监控整个工厂&am…

uniapp路由

1、路由登记 uni-app页面路由为框架统一管理&#xff0c;开发者需要在pages.json里配置每个路由页面的路径及页面样式。 类似小程序在 app.json 中配置页面路由一样。 所以 uni-app 的路由用法与 Vue Router 不同&#xff0c;如仍希望采用 Vue Router 方式管理路由&#xff0c;…

九州金榜|教育孩子小技巧--给孩子讲故事

百年大计&#xff0c;教育为本。对于孩童来讲&#xff0c;故事无疑是最好的教育方法&#xff0c;通过一个个引人入胜的小故事将其中蕴含的道理、经验、哲理、观念传递给孩子。 可以这么说&#xff0c;故事对于儿童获取词汇储备、洞察能力、处事方法、情商和智力的提升都有着极为…

Go 语言如何读取 excel 测试数据,简单易学

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

算法基础day2

前缀和 #include <iostream> using namespace std; const int N100010; int n,m; int a[N],s[N]; int main() {scanf("%d%d",&n,&m);for(int i1;i<n;i) scanf("%d",&a[i]);for(int i1;i<n;i) s[i]s[i-1]a[i];while(m--){int l,r;s…

Gooxi亮相2023中国数据与存储峰会展示最新存储解决方案

今日&#xff0c;以“数智创新&#xff0c;AI未来”为主题的2023 中国数据与存储峰会在北京顺利举行&#xff0c;Gooxi受邀参与并展示了最新存储服务器产品&#xff0c;搭配最新处理器平台&#xff0c;能够广泛应用在人工智能、数据分析、云端计算、数据存储和高性能计算等领域…

兔子目标检测数据集VOC格式3900张

兔子是一类可爱的哺乳动物&#xff0c;拥有圆润的脸庞和长长的耳朵&#xff0c;身体轻盈柔软。它们通常是以温和和友善的形象出现在人们的视野中&#xff0c;因此常常成为童话故事和卡通形象中的角色。 兔子是草食性动物&#xff0c;主要以各种草本植物为食&#xff0c;包括草…

python查找mongo中符合条件的json记录

一、需求&#xff1a; 之前有次需要临时查找mongo中存储的json串&#xff0c;符合特定条件的记录&#xff1b; 举个例子&#xff0c;mongo中记录如下图&#xff1a; 其中每条存储的数据大概为&#xff1a; [{"createUser": "Zxtech","paramName&qu…