数据结构与算法:堆

朋友们大家好啊,本篇文章来到堆的内容,堆是一种完全二叉树,再介绍堆之前,我们首先对树进行讲解

树与堆

  • 1.树的介绍
    • 1.1节点的分类
  • 2.树的存储结构
  • 3.二叉树的概念和结构
    • 3.1 二叉树的特点
    • 3.2 特殊的二叉树
    • 3.3二叉树的存储结构
  • 4.堆的介绍和实现
    • 4.1 堆的实现,初始化与销毁
    • 4.2插入元素与向上调整
      • 4.2.1堆向上调整
      • 4.2.2堆的建立
      • 4.2.3 堆元素的删除和向下调整
    • 4.3 获取堆顶元素与堆的数据个数
    • 4.4判断堆是否为空

1.树的介绍

在这里插入图片描述

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合,n=0时成为空树,当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1、T2、……、Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)。

  • 有一个特殊的结点,称为根结点(A),根节点没有前驱结点。n>0 时根结点是唯一的,不可能存在多个根节点
  • 每棵子树的根结点有且只有一个前驱可以有0个或多个后继

注意:树形结构中,子树之间不能有交集,否则就不是树形结构
在这里插入图片描述
这两种情况就是错误的

1.1节点的分类

树的结点包含一个数据元素及若干指向其子树的分支。结点拥有的子树数称为结点的度(Degree)度为0的结点称为叶结点(Leaf)或终端结点度不为0的结点称为非终端结点或分支结点。除根结点之外,分支结点也称为内部结点。树的度是树内各结点的度的最大值。如图所示,这棵树结点的度的最大值是结点D的度为3,所以树的度为3
在这里插入图片描述

结点的子树的根称为该结点的孩子,相应地,该结点称为孩子的双亲同一个双亲的孩子之间互称兄弟。结点的祖先是从根到该结点所经分支上的所有结点。

在这里插入图片描述
结点的层次(Level)从根开始定义起,根为第一层,根的孩子为第二层。若某结点在第L层,则其子树的根就在第L+1层。其双亲在同一层的结点互为堂兄弟。显然 D、E、F是堂兄弟。树中结点的最大层次称为树的深度(Depth)或高度,当前树的深度为4。

2.树的存储结构

提到存储结构,我们会想到两种:顺序存储和链式存储
先来看看顺序存储结构,用一段地址连续的存储单元依次存储线性表的数据元素。这对于线性表来说是很自然的

树中某个结点的孩子可以有多个,这就意味着,无论按何种顺序将树中所有结点存储到数组中,结点的存储位置都无法直接反映逻辑关系,你想想看,数据元素挨个的存储,谁是谁的双亲,谁是谁的孩子呢?简单的顺序存储结构是不能满足树的实现要求的。

树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法等。我们这里就简单的了解其中最常用的孩子兄弟表示法

任意一棵树,它的结点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。因此,我们设置两个指针,分别指向该结点的第一个孩子和此结点的右兄弟。
在这里插入图片描述

其中data是数据域,firstchild为指针域,存储该结点的第一个孩子结点的存储地址,rightsib是指针域,存储该结点的右兄弟结点的存储地址。

typedef int DataType;
struct Node
{struct Node* firstchild; // 第一个孩子结点struct Node* rightsib; // 指向其下一个兄弟结点DataType data; // 结点中的数据域
};

在这里插入图片描述

3.二叉树的概念和结构

二叉树(Binary Tree)是n(n≥0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成。
在这里插入图片描述

3.1 二叉树的特点

  • 每个结点最多有两棵子树,所以二叉树中不存在度大于2的结点。注意不是只有两棵子树,而是最多有。没有子树或者有一棵子树都是可以的。
  • 左子树和右子树是有顺序的,次序不能任意颠倒。
  • 即使树中某结点只有一棵子树,也要区分它是左子树还是右子树。

二叉树具有五种基本情况:
在这里插入图片描述

3.2 特殊的二叉树

在这里插入图片描述

  • 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。一个树的层数为K,且节点总数为2k-1,则它就是满二叉树
    单是每个结点都存在左右子树,不能算是满二叉树,还必须要所有的叶子都在同一层上,这就做到了整棵树的平衡。

  • 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

完全二叉树的特点:

  • (1)叶子结点只能出现在最下两层。
  • (2)最下层的叶子一定集中在左部连续位置。
  • (3)倒数二层,若有叶子结点,一定都在右部连续位置
  • (4)如果结点度为1,则该结点只有左孩子,即不存在只有右子树的情况
  • (5)同样结点数的二叉树,完全二叉树的深度最小

完全二叉树的性质

  1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有 2i-1 个结点
  2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是 2h-1
  3. 对任何一棵二叉树, 如果度为0其叶结点个数为n0,度为2的分支结点个数为n2,则有n0=n2+1
  4. 具有n个节点的完全二叉树的深度为[log2n]+1

对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为i的结点有:
5. 若i>0,i位置节点的双亲序号(i-1)/2;i=0,i为根节点编号,无双亲节点
6. 若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子
7. 若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子

3.3二叉树的存储结构

前面我们已经谈到了树的存储结构,并且谈到顺序存储对树这种一对多的关系结构实现起来是比较困难的。但是二叉树是一种特殊的树,由于它的特殊性,使得用顺序存储结构也可以实现。
二叉树的顺序存储结构就是用一维数组存储二叉树中的结点,并且结点的存储位置,也就是数组的下标要能体现结点之间的逻辑关系,比如双亲与孩子的关系,左右兄弟的关系等
在这里插入图片描述
将这棵二叉树存入到数组中,相应的下标对应其同样的位置
在这里插入图片描述
在这里插入图片描述
考虑一种极端的情况,一棵深度为k的右斜树,它只有k个结点,却需要分配2k一1个存储单元空间,这显然是对存储空间的浪费,如图:
在这里插入图片描述

所以,顺序存储结构一般只用于完全二叉树

4.堆的介绍和实现

堆是一棵完全二叉树,堆中的每一个节点都满足堆性质,也就是每个节点的值都必须大于(或等于)或小于(或等于)其子节点的值。根据这个性质,堆可以分为两种类型:

  • 大堆:在大堆中,每个父节点的值都大于或等于其子节点的值。因此,堆的根节点(即堆顶)包含了堆中的最大值
  • 小堆:在小堆中,每个父节点的值都小于或等于其子节点的值。因此,堆的根节点包含了堆中的最小值

下面是一个小堆的结构:

       1/   \3     6/ \   / \5  9  8   13

在这个小堆中:

  • 根节点1是最小的元素。
  • 每个子节点3, 6的值都大于等于它们的父节点1的值。
  • 这个性质适用于堆的所有层:例如,节点5, 9, 8, 13的值都大于等于它们各自的父节点3, 6的值。

这个小堆对应数组存储结构为1 3 6 5 9 8 13

下面是一个大堆的结构:

       13/    \9      8/ \    / \5  3   6   1

对应数组结构为13 9 8 5 3 6 1

堆的树形结构只是一种抽象的概念,在实际的物理存储上,堆通常是以数组的形式来实现的

4.1 堆的实现,初始化与销毁

堆的成立是数组数据不断调整的过程,这里我们创建出堆的框架:

typedef int HPDataType;typedef struct Heap
{HPDataType* a;int size;int capacity;
}Heap;

初始化

void HeapInit(Heap* php)
{assert(php);php->a = NULL;php->size = 0;php->capacity = 0;
}

初始化堆数据数组的指针为 NULL。这意味着堆开始时没有分配任何内存用于存储元素。通常,在第一次向堆中添加元素时,程序会根据需要分配内存

销毁

void HeapDestroy(Heap* php)
{assert(php);free(php->a);php->size = 0;php->capacity = 0;
}

free 函数释放堆结构中动态分配的数组 a 所占用的内存。php->a 是指向堆中元素数组的指针,在堆初始化或元素添加过程中,会通过 malloc、realloc 等动态内存分配函数分配内存。释放这块内存是防止内存泄露的重要步骤。释放后,这块内存不应再被访问

4.2插入元素与向上调整

void HeapPush(Heap* php, HPDataType x)
{assert(php);if (php->size == php->capacity){int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);if (tmp == NULL){perror("realloc fail");exit(-1);}php->capacity = newcapacity;php->a = tmp;}php->a[php->size] = x;php->size++;Ajustup(php->a, php->size - 1);
}

首先判断php不为空,再进行扩容,这个扩容在前面有多次提到
最主要的是下面的Ajustup函数

4.2.1堆向上调整

我们这里以小堆为例进行讲解:

当向堆中插入一个新元素后,为了维持小顶堆的性质(即父节点的值始终小于等于其子节点的值),可能需要进行元素的向上调整)。下面详细说明这个过程:

  1. 当一个新元素被加入到堆中时,它首先被放置在堆的末尾(即作为树的最底层的最右侧的叶子节点),以保持完全二叉树的形状。
  2. 比较新节点与其父节点的值:插入的新元素可能会破坏小顶堆的性质,此时需要将新元素与其父节点进行比较。对于数组中的节点 i(假设索引从0开始),其父节点的位置是 (i - 1) / 2注意这里全是整数值比如下标为2的元素,它的父节点就为0
  3. 如果新元素的值小于其父节点的值,那么就需要交换这两个节点的值,因为在小顶堆中父节点应当是小于或等于子节点的值
  4. 向上递归:继续将现在的节点位置(原父节点的位置,因为已经交换)与新的父节点进行比较,如果还是小新的父节点的值,继续交换。这一过程一直进行,直到新元素到达根节点,或新元素大于或等于其父节点的值。

在这里插入图片描述
接下来我们用函数实现

void Ajustup(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 = (parent - 1) / 2;}elsebreak;}
}
  • 对于给定的子节点索引child,其父节点的索引计算为(child - 1) / 2
  • 循环条件:while (child > 0)循环确保我们不会尝试移动根节点(因为根节点的索引为0,没有父节点)。循环继续执行,只要当前节点的索引大于0。
  • 完成交换后,更新child变量为原父节点的索引,因为交换后当前元素已经移动到了父节点的位置。然后,对新的child值重新计算parent索引,继绀执行可能的进一步交换
  • 循环终止条件:如果当前节点的值不小于其父节点的值(即堆的性质得到了满足),循环终止,else break;执行

补充Swap函数:

void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2; *p2 = tmp;
}

有了这个调整函数,我们就可以建堆了

4.2.2堆的建立

通过调用Ajustup函数,逐步把输入数组a转换成一个小堆

我们在主函数中进行测试
在这里插入图片描述

在这里插入图片描述
这个经验证确实是一个小堆

4.2.3 堆元素的删除和向下调整

堆默认规定,要删除根节点的数据

堆顶存放最小值,删除后,为了满足小堆的性质,接下来根节点存储的为次小值

  • 由于堆是以数组的形式存储的,堆顶元素就是数组的第一个元素。删除堆顶元素后,需要保持堆的完整性和顺序特性

  • 将堆的最后一个元素移动到堆顶:为了保持结构性质,堆的最后一个元素被移动到堆顶位置。这是因为在二叉堆中,我们希望维护一个完全二叉树的结构。使用最后一个元素来替代被删除的元素是一种简单且有效的方法,它保证了树的结构完整性。

  • 移动最后一个元素到堆顶后,这个新的堆顶元素可能会破坏堆的顺序性质。为了恢复堆的性质,需要执行下沉操作。具体步骤如下:

    • 比较新的堆顶元素与其子节点。
    • 如果在最小堆中,新的堆顶元素比其子节点大,则它需要与其最小的子节点交换位置; 在最大堆中,如果新的堆顶元素比其子节点小,则它需要与其最大的子节点交换位置。
    • 重复这个比较和交换过程,直至新的堆顶元素被移至正确的位置,也就是说,它不再比任何一个子节点大(在最小堆中)或小(在最大堆中)
void HeapPop(Heap* php)
{assert(php);assert(php->size > 0);Swap(&php->a[0], &php->a[php->size - 1]);php->size--;Ajustdown(php->a,php->size,0);
}

在这里插入图片描述
向下调整函数

void Ajustdown(HPDataType* a, int size, int parent)
{int child = parent * 2 + 1;while (child<size){if (child + 1 < size && a[child + 1] < a[child])//防止只有左孩子而越界{child++;}if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = child * 2 + 1;}else{break;}}
}

我们需要找小一点的孩子进行交换

  1. 子节点选择:计算左子节点的索引(child = parent * 2 + 1)。在二叉堆中,给定父节点索引为i的情况下,左子节点的索引为2*i + 1右子节点的索引为2*i + 2。开始时,我们先考虑左子节点。
  2. while循环:确保当前考虑的子节点索引没有超出数组的界限,如果有两个节点,判断右节点是否小于左节点,如果小,child++,后面让右孩子与父节点交换
  3. 更新parent索引为当前child的索引,继续向下遍历堆。更新child索引为新parent索引的左子节点,准备进行下一轮的比较。
  4. 结束循环:如果子节点的值不小于父节点的值,说明当前父节点的位置适当,堆的性质得以维持,此时循环可以终止

对于每次AdjustDown调用,最坏情况下需要进行的比较和交换次数与堆的高度成正比,即O(log n)

AdjustDown操作的时间复杂度是O(log n)

4.3 获取堆顶元素与堆的数据个数

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

4.4判断堆是否为空

bool HeapEmpty(Heap* php)
{assert(php);return php->size == 0;
}

如果是空,返回true,不是则返回false

本节内容到此结束,感谢大家观看!!!

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

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

相关文章

Mybatis | 动态SQL

目录: 动态SQL中的 “元素” :\<if>元素\<choose>、\<when>、\<otherwise>元素\<where>、\<trim>元素\<set>元素\<foreach>元素\<bind>元素 作者简介 &#xff1a;一只大皮卡丘&#xff0c;计算机专业学生&#xff0c;正…

单细胞Seurat - 降维与细胞标记(4)

本系列持续更新Seurat单细胞分析教程&#xff0c;欢迎关注&#xff01; 非线形降维 Seurat 提供了几种非线性降维技术&#xff0c;例如 tSNE 和 UMAP&#xff0c;来可视化和探索这些数据集。这些算法的目标是学习数据集中的底层结构&#xff0c;以便将相似的细胞放在低维空间中…

Python错题集-4:NameError:(变量名错误)

1问题描述 Traceback (most recent call last): File "D:\pycharm\projects\1-可视化学习\8.3更改小提琴图的中位数、均值、颜色等.py", line 8, in <module> violin_parts plt.violinplot(data, showmediansTrue, showmeansTrue) …

【MATLAB源码-第150期】基于matlab的开普勒优化算法(KOA)机器人栅格路径规划,输出做短路径图和适应度曲线。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 开普勒优化算法&#xff08;Kepler Optimization Algorithm, KOA&#xff09;是一个虚构的、灵感来自天文学的优化算法&#xff0c;它借鉴了开普勒行星运动定律的概念来设计。在这个构想中&#xff0c;算法模仿行星围绕太阳的…

项目风险:测试大佬结合实例告诉你如何应对!

项目有风险 今天下午15点&#xff0c;团队成员D向他的主管Z反馈他测试的项目有风险&#xff1a;项目在测试周期内&#xff0c;但在用例评审时发现有一处功能逻辑有争议&#xff0c;需要产品经理跟业务方确认&#xff0c;可能出现的情况有&#xff1a; 1 不变更需求&#xff0…

【深入了解设计模式】组合设计模式

组合设计模式 组合模式是一种结构型设计模式&#xff0c;它允许你将对象组合成树状结构来表现“整体-部分”关系。组合模式使得客户端可以统一对待单个对象和组合对象&#xff0c;从而使得代码更加灵活和易于扩展。 概述 ​ 对于这个图片肯定会非常熟悉&#xff0c;上图我们可…

Carla自动驾驶仿真九:车辆变道路径规划

文章目录 前言一、关键函数二、完整代码效果 前言 本文介绍一种在carla中比较简单的变道路径规划方法&#xff0c;主要核心是调用carla的GlobalRoutePlanner模块和PID控制模块实现变道&#xff0c;大体的框架如下图所示。 一、关键函数 1、get_spawn_point(),该函数根据指定r…

力扣hot100题解(python版41-43题)

41、二叉树的层序遍历 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;[[3],[9,20],[15,7]]示例…

【C语言结构体】用户自定义类型--结构体,结构体传参,位段,联合体和枚举【图文详解】

欢迎来CILMY23的博客喔&#xff0c;本篇为【C语言结构体】用户自定义类型--结构体&#xff0c;结构体传参&#xff0c;位段&#xff0c;联合体和枚举【图文详解】&#xff0c;感谢观看&#xff0c;支持的可以给个一键三连&#xff0c;点赞关注收藏。 前言 上一篇&#xff08;ht…

GO—函数

Go 语言支持普通函数、匿名函数和闭包&#xff0c;从设计上对函数进行了优化和改进&#xff0c;让函数使用起来更加方便。 Go 语言的函数属于“一等公民”&#xff08;first-class&#xff09;&#xff0c;也就是说&#xff1a; 函数本身可以作为值进行传递。支持匿名函数和闭…

推荐6款SSH远程连接工具

1、Xshell 介绍&#xff1a; xshell是一个非常强大的安全终端模拟软件&#xff0c;它支持SSH1, SSH2, 以及Windows平台的TELNET 协议。Xshell可以在Windows界面下用来访问远端不同系统下的服务器&#xff0c;从而比较好的达到远程控制终端的目的。 业界最强大的SSH客户机 官…

数据分析-Pandas数据的直方图探查

数据分析-Pandas数据的直方图探查 数据分析和处理中&#xff0c;难免会遇到各种数据&#xff0c;那么数据呈现怎样的规律呢&#xff1f;不管金融数据&#xff0c;风控数据&#xff0c;营销数据等等&#xff0c;莫不如此。如何通过图示展示数据的规律&#xff1f; 数据表&…

农产品质量追溯系统—功能介绍(2)

储藏管理 储藏信息管理对需要储藏的农产品,记录储藏的相关信息,如储藏开始时间、存放仓库、操作人员、储藏原因等; 仓库信息管理物流管理 物流公司管理对相关的物流公司信息进行登记,以便于管理和追溯; 车辆管理

我的秋招数据分析岗面经分享(京东,美团,阿里,拼多多,vivo,滴滴)

节前&#xff0c;我们社群组织了一场技术&面试讨论会&#xff0c;邀请了一些互联网大厂同学、参加社招和校招面试的同学&#xff0c;针对新手如何入门数据分析、机器学习算法、该如何备战面试、面试常考点分享等热门话题进行了深入的讨论。 基于社群的讨论&#xff0c;今天…

OpenHarmony、HarmonyOS打开编辑 PDF 等操作的三方组件使用教程

项目场景: 随着数字化时代的发展,PDF 文档成为广泛应用于各行业的重要文件格式。为了提高OpenHarmony/HarmonyOS生态系统的功能性和用户体验,我们需要一款支持打开、编辑PDF文件的应用程序。 使用户能够轻松打开、浏览和编辑PDF文件。该应用将充分利用OpenHarmony/HarmonyO…

【NTN 卫星通信】卫星和无人机配合的应用场景

1 场景概述 卫星接入网是一种有潜力的技术&#xff0c;可以为地面覆盖差地区的用户提供无处不在的网络服务。然而&#xff0c;卫星覆盖范围对于位于考古或采矿地点内部/被茂密森林覆盖的村庄/山谷/靠近山丘或大型建筑物的用户可能很稀疏。因此&#xff0c;涉及卫星接入和无人驾…

HarmonyOS Full SDK的安装

OpenHarmony的应用开发工具HUAWEI DevEco Studio现在随着OpenHarmony版本发布而发布,只能在版本发布说明中下载,例如最新版本的OpenHarmony 4.0 Release。对应的需要下载DevEco Studio 4.0 Release,如下图。 图片 下载Full SDK主要有两种方式,一种是通过DevEco Studio下载…

教你用Fiddler捕获HTTPS请求

安装Fiddler 这里不特别说明了&#xff0c;网上搜索一大把&#xff0c;根据安装引导一步步安装即可。&#xff08;这里采用的是fiddler v4.6&#xff09; 配置Fiddler 1、打开fiddler配置Tools –>Telerik Fiddler Options。 2、打开HTTPS配置项&#xff0c;勾选“Captur…

ctf_show笔记篇(web入门---爆破)

爆破 21&#xff1a;直接bp抓包跑字典&#xff0c;需base64加密 22&#xff1a;可用工具跑也可用浏览器找还可以用网上做好的域名查找去找 23&#xff1a;此题需跑脚本已经附上自写脚本 最后跑出来六个答案一个一个尝试得到答案为3j import hashlibm "0123456789qwert…

C++_AVL树

目录 1、AVL的概念 2、平衡因子的调整概念 3、AVL树的插入 3.1 调整平衡因子代码实现 3.2 右旋操作 3.2 左旋操作 3.3 双旋-先右旋再左旋 3.4 双旋-先左旋再右旋 3.5 旋转操作的小结 4、AVL的验证与实现 结语 前言&#xff1a; 在C中&#xff0c;AVL树是在二叉搜索…