当前位置: 首页 > news >正文

数据结构-堆-详解

数据结构-堆-详解

  • 1.性质
      • 大根堆
      • 小根堆
  • 2.实现
    • 2.1struct Heap、HeapInit、HeapDestroy
    • 2.2HeapPush
      • AdjustUp
    • 2.3HeapPop
      • AdjustDown
    • 2.4HeapTop、HeapSize、HeapEmpty
  • 3.应用
    • 3.1堆排
      • 建堆
      • 排序
    • 3.2TopK问题

1.性质

堆是一种特殊的完全二叉树,其父节点总是不大于/不小于 子节点。
相比于普通二叉树,堆更适合用顺序结构的数组存储。

大根堆

其中任一父节点都不小于子节点。
逻辑结构:
在这里插入图片描述
储存结构:

在这里插入图片描述

小根堆

其中任一父节点都不大于子节点。
逻辑结构:
在这里插入图片描述
储存结构:
在这里插入图片描述

2.实现

2.1struct Heap、HeapInit、HeapDestroy

堆的结构体、堆的初始化、堆的销毁。
从上面的大小根堆可以看出,在实现上是一个顺序表,而逻辑上是二叉树
因此,这几步与顺序表几乎完全一致:

typedef int HeapDataType;
typedef struct Heap
{HeapDataType* a;int size;int capacity;
}Heap;void HeapInit(Heap* php)
{assert(php);php->a = (HeapDataType*)malloc(sizeof(HeapDataType) * 4);if (!php->a){perror("HeapInit::malloc");return;}php->size = 0;php->capacity = 4;
}void HeapDestroy(Heap* php)
{assert(php);free(php->a);php->a = NULL;php->capacity = php->size = 0;
}

2.2HeapPush

插入元素。

void HeapPush(Heap* php, HeapDataType x)
{assert(php);if (php->size == php->capacity){HeapDataType* tmp = (HeapDataType*)realloc(php->a,sizeof(HeapDataType) * php->capacity * 2);if (!tmp){perror("HeapDataType::realloc");return;}php->a = tmp;php->capacity *= 2;}php->a[php->size] = x;php->size++;AdjustUp(php->a,php->size-1);
}

先判断是否需要扩容,再插入。
但这是个堆,因此需要对数据进行调整。
此处以大根堆为例,使用向上调整法AdjustUp

AdjustUp

大根堆需要满足其中任一父节点都不小于子节点。
当插入一个节点后,可以将其与父节点比较,不满足条件则交换,最多调到根。

void AdjustUp(HeapDataType* a, int child)
{assert(a);int parent = (child - 1) / 2;while (parent >= 0){if (a[child] > a[parent])Swap(a + child, a + parent);elsebreak;child = parent;parent = (child - 1) / 2;}
}//有小bug

由公式得:父节点下标parent = (child - 1) / 2
进入循环:

  • 如果不满足条件,则交换Swap
void Swap(HeapDataType* a,HeapDataType* b)
{assert(a && b);HeapDataType tmp = *a;*a = *b;*b = tmp;
}

这里又封装了一个函数,因为这部分需要用到交换的地方还挺多的。

  • 如果满足条件,由于堆的性质,可直接break结束循环。

一个小bug:需注意的是循环结束条件parent >= 0,当运行时,会发现程序能跑,没出问题。
但可以分析一下:如果child已经为根,即child == 0,算一下parent(0 - 1)/2 = 0
下图的情景出现了!
在这里插入图片描述
当然,这里的bug是能改的,用child作为判断条件即可:

//while (parent >= 0)
while(child>0)

2.3HeapPop

删除堆顶元素。

堆顶元素为堆的最大/小值,因此,删除堆顶元素才具有一定意义。

挪动删除,
是不行的:
在这里插入图片描述
两个原因:

  • 效率低下
  • 父子关系被打乱

正确方法:

void HeapPop(Heap* php)
{assert(php);assert(!HeapEmpty(php));Swap(php->a, php->a + php->size - 1);php->size--;AdjustDown(php->a, php->size, 0);
}

这里,先将最前的与最后的交换(Swap),
然后删除最后一个元素(size--),
最后,向下调整(AdjustDown)。
在这里插入图片描述

AdjustDown

void AdjustDown(HeapDataType* a, int n, int parent)
{assert(a);int child = parent * 2 + 1;while (child < n){if ((child + 1) < n && a[child] < a[child + 1])child++;if (a[parent] < a[child])Swap(a + parent, a + child);elsebreak;parent = child;child = parent * 2 + 1;}
}

这里传了三个参数,其中parent为需要调整的父节点的位置,此处传0
while循环,会在child下标小于堆大小n的情况下继续执行,这表示还有子节点存在。
由于是大根堆,向下调整时,先选出子节点中的较大值,再与父节点比较,满足则break出循环,不满足条件则交换,继续循环。

2.4HeapTop、HeapSize、HeapEmpty

取堆顶元素、堆的大小、判空。

HeapDataType HeapTop(Heap* php)
{assert(php);return php->a[0];
}int HeapSize(Heap* php)
{assert(php);return php->size;
}bool HeapEmpty(Heap* php)
{assert(php);return php->size == 0;
}

3.应用

3.1堆排

即使用堆进行排序。
给一个数组,要使用堆排,前提是必须有个堆,因此第一步为建堆。
那么,建大堆还是小堆?怎么建堆?
建什么堆,这里有个规律:

  • 升序建大堆
  • 降序建小堆

如何建堆,有两种方法:

  • 使用向上调整法,插入建堆
  • 使用向下调整法,调整建堆

建堆

向上调整法

void HeapSort(int* a, int n)
{//建堆for (int i = 0; i < n; i++){AdjustUp(a, i);}//排序
}

向下调整法
使用向下调整法建堆,需满足左、右子树必须是堆
随便给的数组肯定不能满足此条件,因此不能从根节点开始建堆,需要找倒数第一个非叶子节点:
在这里插入图片描述
叶节点是堆,空节点也是堆,因此,从第一个非叶子节点开始使用向下调整法,不断调整直到根节点。

void HeapSort(int* a, int n)
{//建堆for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, n, i);}//排序
}

在实际使用中,通常使用向下调整法建堆,因为向上调整法的时间复杂度为O(N*logN),而向下调整法的时间复杂度为O(N)

排序

这里使用的是堆删除的思想来排序。
现在假设排升序,并且已经建好了一个大堆:
在这里插入图片描述
在这里插入图片描述
Pop一下,最大的就跑最后去了,然后重复此过程,就能排成升序。
这也验证了:

  • 升序建大堆
  • 降序建小堆

完整代码:

void HeapSort(int* a, int n)
{//建堆for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, n, i);}//排序int size = n;while (size--){Swap(a, a + size);AdjustDown(a, size, 0);}
}

3.2TopK问题

即在很多数中找出最…K个 数。
这里的很多一般是真的很多,因此不能在对全部数据进行排序,因为浪费空间。
解决方法是用这些数的前K个建个堆,在将其余满足条件的数插入堆中。

建堆:

  • 求最大,建小堆
  • 求最小,建大堆

插入:

依次将其余的数与堆顶的数进行比较,根据需要进行替换,最后堆中的K个数即为所求。


希望本篇文章对你有所帮助!并激发你进一步探索数据结构的兴趣!

本人仅是个C语言初学者,如果你有任何疑问或建议,欢迎随时留言讨论!让我们一起学习,共同进步!

相关文章:
数据结构-二叉树-基础知识

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【机器学习】K近邻
  • 基于UE5和ROS2的激光雷达+深度RGBD相机小车的仿真指南(五):Blender锥桶建模
  • C++语法知识点合集:11.模板
  • 锡林郭勒奶酪品牌呼和浩特市大召店盛大开业
  • Kafka【八】如何保证消息发送的可靠性、重复性、有序性
  • 什么是 TDengine?
  • 【机器学习】高斯网络的基本概念和应用领域
  • Python | Leetcode Python题解之第394题字符串解码
  • [数据集][目标检测]抽烟检测数据集VOC+YOLO格式22559张2类别
  • 外卖霸王餐对接接口为用户提供了哪些好处?
  • OpenVPN的测试主要包括安装客户端、配置连接、连接测试以及网络验证等步骤。以下是一个详细的测试流程:
  • 合宙LuatOS开发板Core_Air780EP使用说明
  • Android12上新增jar遇到的问题总结
  • 代码随想录Day39|322. 零钱兑换、279.完全平方数、139.单词拆分
  • Flask中的上下文(Context)
  • IE9 : DOM Exception: INVALID_CHARACTER_ERR (5)
  • Debian下无root权限使用Python访问Oracle
  • ERLANG 网工修炼笔记 ---- UDP
  • GraphQL学习过程应该是这样的
  • IDEA常用插件整理
  • iOS仿今日头条、壁纸应用、筛选分类、三方微博、颜色填充等源码
  • js操作时间(持续更新)
  • Linux链接文件
  • magento2项目上线注意事项
  • Making An Indicator With Pure CSS
  • Mysql数据库的条件查询语句
  • php面试题 汇集2
  • Python爬虫--- 1.3 BS4库的解析器
  • React as a UI Runtime(五、列表)
  • vue-router 实现分析
  • -- 查询加强-- 使用如何where子句进行筛选,% _ like的使用
  • 程序员该如何有效的找工作?
  • 分享一份非常强势的Android面试题
  • 回顾 Swift 多平台移植进度 #2
  • 聊聊sentinel的DegradeSlot
  • 前端面试总结(at, md)
  • 如何编写一个可升级的智能合约
  • 如何用vue打造一个移动端音乐播放器
  • 使用SAX解析XML
  • 世界上最简单的无等待算法(getAndIncrement)
  • 我感觉这是史上最牛的防sql注入方法类
  • ​软考-高级-系统架构设计师教程(清华第2版)【第1章-绪论-思维导图】​
  • ​软考-高级-信息系统项目管理师教程 第四版【第14章-项目沟通管理-思维导图】​
  • ​云纳万物 · 数皆有言|2021 七牛云战略发布会启幕,邀您赴约
  • ### Error querying database. Cause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException
  • $.ajax中的eval及dataType
  • (1)STL算法之遍历容器
  • (Pytorch框架)神经网络输出维度调试,做出我们自己的网络来!!(详细教程~)
  • (二)hibernate配置管理
  • (附源码)springboot宠物管理系统 毕业设计 121654
  • (附源码)springboot课程在线考试系统 毕业设计 655127
  • (附源码)ssm教师工作量核算统计系统 毕业设计 162307
  • (回溯) LeetCode 78. 子集
  • (简单有案例)前端实现主题切换、动态换肤的两种简单方式
  • (介绍与使用)物联网NodeMCUESP8266(ESP-12F)连接新版onenet mqtt协议实现上传数据(温湿度)和下发指令(控制LED灯)