【数据结构】堆的基本实现

目录

  • 1、堆的概念和结构
  • 2、堆基本功能实现
    • 2.1 初始化和销毁
    • 2.2 打印
    • 2.3 向上调整
    • 2.4 插入
    • 2.5 向下调整
    • 2.6 删除
    • 2.7 堆判断是否为空
    • 2.8 取堆顶数据
    • 2.9 堆的有效数据
  • 3、功能测试
  • 4、总结


1、堆的概念和结构

首先说明:这里的堆是数据结构里面的堆,并不是操作系统里的堆区

堆本质上是数组实现优先级队列功能,但是表示成一颗完全二叉树,物理上是数组,逻辑上是完全二叉树,前n-1层满节点,最后一层从左到右连续

堆分为两种情况,最大堆(大根堆)最小堆(小根堆)

  • 最大堆:所有的父节点都大于等于子节点,说明堆顶就是最大值
  • 最小堆:所有的父节点都小于等于子节点,说明堆顶就是最小值
    在这里插入图片描述

2、堆基本功能实现

堆是本质是数组的优先级队列,表示成完全二叉树,物理结构是数组,逻辑结构是完全二叉树
在这里插入图片描述

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>typedef int HPDataType;typedef struct Heap
{HPDataType* a;int size;int capacity;
}Heap;// 初始化
void HeapInit(Heap* ph);// 销毁
void HeapDestroy(Heap* ph);// 打印
void HeapPrint(Heap* ph);// 交换
void Swap(HPDataType* pa, HPDataType* pb);// 向上调整
void AdjustUp(Heap* ph, int child);// 插入
void HeapPush(Heap* ph, HPDataType x);// 向下调整
void AdjustDown(Heap* ph, int parent);// 删除
void HeapPop(Heap* ph);// 取堆顶数据
void HeapTop(Heap* ph);// 堆是否为空
bool HeapEmpty(Heap* ph);// 堆的有效个数
int HeapSize(Heap* ph);

2.1 初始化和销毁

// 初始化
void HeapInit(Heap* ph)
{assert(ph);ph->a = NULL;ph->size = ph->capacity = 0;
}// 销毁
void HeapDestroy(Heap* ph)
{assert(ph);free(ph->a);ph->size = ph->capacity = 0;
}

2.2 打印

由于是堆的性质是队列,本质上是个数组,但是队列通常不能打印,我这里是为了方便查看数据才实现。

// 打印
void HeapPrint(Heap* ph)
{for (int i = 0; i < ph->size; i++){printf("%d ", ph->a[i]);}printf("\n");
}

2.3 向上调整

思路:

  1. 以小根堆为例,所有的parent <= child
  2. 找到需要调整的子节点的父亲,parent = (child - 1) / 2
  3. parent的值比child要大,需要交换值,把新的child置为现在parent,新的parent在用新的child计算得来
  4. 调整完毕分两种情况:
  • 重复步骤3,直到child > 0,这里说明child == 0,就是根节点,不用在调整
  • parent <= child,说明当前堆结构是正常的,不用调整
// 向上调整
// 小堆:所有的父亲要小于等于孩子
void AdjustUp(Heap* ph, int child)
{assert(ph);int parent = (child - 1) / 2;while (child > 0){// 父亲的值大于孩子,需要调整if (ph->a[child] < ph->a[parent]){Swap(&ph->a[child], &ph->a[parent]);child = parent;parent = (child - 1) / 2;}else{// 堆结构正常break;}}
}

2.4 插入

插入的逻辑还是和顺序表类似,但是在最后多了一个向上调整,这也是最关键的,大家可以看动图
这里以小根堆结构为例,插入的逻辑还是和顺序表类似,但是在最后多了一个向上调整,这也是最关键的
因为堆的特性,插入数据后还是需要保持小根堆的结构,正常在后面插入就会破坏堆原有的结构,所以我们插入完数据就需要向上调整,保持堆的特性。

// 插入,建堆
void HeapPush(Heap* ph, HPDataType x)
{assert(ph);// 容量检查和扩容if (ph->size == ph->capacity){int newCapacity = ph->capacity == 0 ? 4 : ph->capacity * 2;HPDataType* tmp = (HPDataType*)realloc(ph->a, newCapacity * sizeof(HPDataType));assert(tmp);ph->a = tmp;ph->capacity = newCapacity;}// 插入数据ph->a[ph->size] = x;ph->size++;// 插入新数据后,堆的结构会被破坏,从最后一个数据开始向上调整AdjustUp(ph, ph->size - 1);
}

2.5 向下调整

思路:

  1. 以小根堆为例,所有的parent <= child
  2. 算出调整的节点的子节点,leftchild = parent * 2 +1
  3. 求出左右子节点哪个更小,要注意可能右子节点不存在的情况,防止越界
  4. 拿小的子节点和父亲比较,parent > child就交换,新的parent置为现在的child,新的child通过新的parent计算出来
  5. 调整完毕分两种情况:
  • 重复步骤3、4,直到child < size,说明父亲节点在叶子结点,没有孩子,不能调整,否则会越界
  • parent <= child,说明当前堆结构是正常的,不用调整
// 向下调整
void AdjustDown(Heap* ph, int parent)
{assert(ph);int child = parent * 2 + 1;// 调整到叶子结点结束while (child < ph->size){// 防止越界:右孩子可能不存在// 求出左右子节点哪个更小if (child + 1 < ph->size && ph->a[child] > ph->a[child + 1]){child++;}// 父亲比子节点大,就交换if (ph->a[parent] > ph->a[child]){Swap(&ph->a[child], &ph->a[parent]);parent = child;child = parent * 2 + 1;}else{// 堆结构正常break;}}
}

2.6 删除

由于队列的特性,我们删除堆顶的数据,就会破坏堆原有的结构
所以需要让堆顶和最后数据交换,size–,就等于删除了原来的栈顶数据相当于出栈
我们在让栈顶向下调整,就可以让堆结构正常

// 删除
void HeapPop(Heap* ph)
{assert(ph);// 空堆不能删除assert(!HeapEmpty(ph));// 交换Swap(&ph->a[0], &ph->a[ph->size - 1]);ph->size--;// 交换后,堆的结构会被破坏,从堆顶开始向下调整让结构正常AdjustDown(ph, 0);
}

2.7 堆判断是否为空

// 堆是否为空
bool HeapEmpty(Heap* ph)
{assert(ph);return ph->size == 0;
}

2.8 取堆顶数据

// 取堆顶数据
void HeapTop(Heap* ph)
{assert(ph);// 空堆不能取assert(!HeapEmpty(ph));return ph->a[0];
}

2.9 堆的有效数据

// 堆的有效个数
int HeapSize(Heap* ph)
{assert(ph);return ph->size;
}

3、功能测试

将乱序数据插入到堆中,打印看是否堆的结构
在这里插入图片描述
很明显结构是正常的小堆
在这里插入图片描述

删除两个数据,堆结构是否还是正常呢
在这里插入图片描述

依旧符合堆的结构,同时也是队列的性质先进先出,数据1和4出队了
在这里插入图片描述

4、总结

学好堆的关键就是对向下调整向上调整的理解,并灵活运用。

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

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

相关文章

Centos中将UTC的时区改为CTS时区

date命令可以看到现在的时间以及时区&#xff0c;可以看到现在是UTC时区 而想要更改时区那么就要了解tzselect命令 tzselect 是一个 Linux 命令行工具&#xff0c;用于交互式地帮助用户选择并设置系统的时区。这个程序会通过一系列的问题引导用户&#xff0c;从而确定用户所在的…

轮子运动学约束

轮式机器人-多种轮子运动学约束 机器人运动学模型的第一步是表达加在单独轮子上的约束。正如在3.2.2节中所示那样,单独轮子的运动以后可以被联合起来计算整个机器人的运动。如在第2章所讨论那样,有四种基本的轮子类型,它们各具变化广泛的运动学参数。所以,我们一开始就要提出对…

一键自动化博客发布工具,用过的人都说好(csdn篇)

CSDN应该是大家接触到最多的博客平台了&#xff0c;所以一款能够发布到CSDN的自动化工具还是非常有必要的。 今天给大家讲讲自动化CSDN博客发布的思路和一些问题的解决办法。 解决问题的思路一定是最重要的&#xff0c;知识是死的&#xff0c;问题是活的&#xff0c;如何在工作…

《深入浅出LLM基础篇》(四):主流大模型介绍

&#x1f389;AI学习星球推荐&#xff1a; GoAI的学习社区 知识星球是一个致力于提供《机器学习 | 深度学习 | CV | NLP | 大模型 | 多模态 | AIGC 》各个最新AI方向综述、论文等成体系的学习资料&#xff0c;配有全面而有深度的专栏内容&#xff0c;包括不限于 前沿论文解读、…

RJ71PB91V 三菱iQ-R系列DP主站/从站模块

RJ71PB91V 三菱iQ-R系列DP主站/从站模块 RJ71PB91V用户手册,RJ71PB91V外部连接 RJ71PB91V参数说明&#xff1a;支持PROFIBUS系统&#xff0c;DP主站/从站。 RJ71PB91V图片 一、三菱PLC DP主站/从站模块RJ71PB91V产品规格说明 [PROFIBUS-DP主站类型] 等级1 主站/从站 [传送规格…

分享一个适用于 Vue3.x 非常好用的组件库【Naive UI】

一、Naive UI 介绍 Naive UI 是一种简单易用、不太复杂的用户界面&#xff08;UI&#xff09;框架&#xff0c;主要用于Web应用程序的开发。它提供了超过80个组件&#xff0c;覆盖了表格、表单、弹窗、图表等多个方面&#xff0c;这些组件不仅功能强大&#xff0c;而且高度可定…

python微信小程序 uniapp高校打印店预约服务系统

本系统是针对校园自助打印开发的工作管理系统&#xff0c;包括到所有的工作内容。可以使自助打印的工作合理化和流程化。本系统包括手机端设计和电脑端设计&#xff0c;有界面和数据库。本系统的使用角色分为管理员和用户、店长三个身份。管理员可以管理系统里的所有信息。店长…

视频号小店怎么选品?给大家分享三个选品思维,让你快速脱颖而出

哈喽&#xff0c;大家好&#xff0c;我是电商花花&#xff0c;专注做电商的花花。 为什么我会说视频号小店是我们今年翻身&#xff0c;赚钱的最佳选择&#xff1f; 因为现在视频号小店不管是在流量上还是市场上&#xff0c;视频号小店都有着属于自己的优势&#xff0c;只要我…

拿走不谢,送你一份HCIA自学攻略

HCIE固然是许多网络工程师梦寐以求的认证&#xff0c;但攀登这座高峰之前&#xff0c;先要稳扎稳打地从华为认证HCIA开始。 对于零基础的学员来说&#xff0c;自学HCIA不仅是一个挑战&#xff0c;更是一次宝贵的学习机会&#xff0c;为以后学习IP、IE打基础。 以HCIA数通方向…

学生寝室人走断电控制系统

学生寝室人走断电控制器石家庄光大远通电气有限公司断电系统由人员探测模块&#xff08;安装在房间内部&#xff09;、宿舍用电控制模块&#xff08;安装在房间供电线路&#xff09;&#xff0c;智能数据网关及后台服务器组成&#xff0c;每个房间安装一台探测器&#xff0c;实…

EPS软件两点绘制檐廊

1、如下图所示&#xff1a; 2、点击檐廊&#xff0c;如下&#xff1a; 在檐廊线状态下&#xff0c;先点击右边的檐廊点&#xff0c;然后将鼠标移至房子边线处&#xff0c;按下【shiftD】键&#xff0c;然后点击左边的檐廊点&#xff0c;勾选右侧的&#xff08;三点闭合生成矩形…

inline和static底层浅析

找一段代码来底层分析 #include<iostream> using namespace std;inline int add(int a, int b) {return a + b