<数据结构与算法>二叉树堆的实现

目录

前言

一、树的概念及结构

1 树的概念

2 树的相关概念

 二、二叉树的概念及结构

1.二叉树的概念

2. 特殊的二叉树

3. 二叉树的性质

4.二叉树的存储结构

 三、二叉树的顺序结构及实现 

1.堆的性质

2.堆的插入

3.堆的实现

堆的结构体

HeapInit 初始化

HeapPush 插入

HeapPop 删除

HeapTop 堆顶元素 

HeapEmpty 判空函数

HeapSize 数据个数

4.堆的代码 

Heap.h

Heap.c

Test.c


前言

        我们之前所学的结构属于线性结构,而树是一种非线性的数据结构,它是由nn>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。


一、树的概念及结构

1 树的概念

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合把它叫做树是因 为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的

  • 有一个特殊的结点,称为根结点根节点没有前驱结点
  • 除根节点外,其余结点被分成M(M>0)个互不相交的集合T1T2……Tm,其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继。因此,树是递归定义的。

 注意:树形结构中,子树之间不能有交集,否则就不是树形结构

2 树的相关概念

  •  节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为6
  • 叶节点或终端节点度为0的节点称为叶节点; 如上图:BCHI...等节点为叶节点
  • 非终端节点或分支节点:度不为0的节点; 如上图:DEFG...等节点为分支节点
  • 双亲节点或父节点若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:AB的父节点
  • 孩子节点或子节点一个节点含有的子树的根节点称为该节点的子节点; 如上图:BA的孩子节点
  • 兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:BC是兄弟节点
  • 树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6
  •  节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
  • 树的高度或深度树中节点的最大层次; 如上图:树的高度为4
  • 堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:HI互为兄弟节点
  • 节点的祖先从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
  • 子孙以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
  • 森林:mm>0)棵互不相交的树的集合称为森林;  

树的一种结构体储存形式:左孩子右兄弟

typedef int DataType;
struct TreeNode
{struct TreeNode* pNextBrother;struct TreeNode* firestChild1;DataType data;};

树在实际中的运用,例如树状目录结构

在数据结构中,我们基本不使用多分枝树这一结构,而使用特殊的树——二叉树

 二、二叉树的概念及结构

1.二叉树的概念

一棵二叉树是结点的一个有限集合,该集合:

1. 或者为空

2. 由一个根节点加上两棵别称为左子树右子树的二叉树组成

 注意:

1. 二叉树不存在度大于2的结点

2. 二叉树的子树有左右之分次序不能颠倒因此二叉树是有序树

2. 特殊的二叉树

满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是2^k - 1 ,则它就是满二叉树。

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

前h-1层是满的,最后一层是连续的

 3. 二叉树的性质

1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^( i - 1) 个结点.

2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是 2^h - 1

3. 对任何一棵二叉树, 如果度为0其叶结点个数为n0 , 度为2的分支结点个数为 n2,则有 n0= n2+1

4. 若规定根节点的层数为1,具有n个结点的满二叉树的深度

5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为i的结点有:  

  • i>0i位置节点的双亲序号:(i-1)/2i=0i为根节点编号,无双亲节点
  • 2i+1<n,左孩子序号:2i+12i+1>=n否则无左孩子
  • 若2i+2<n,右孩子序号:2i+22i+2>=n否则无右孩子

例题1:

度为0的结点数为N0

度为1的结点数为N1

度为2的结点数为N2

2n = N0 + N1 + N2

2n = N0 + N1 + N0 - 1

又因为是完全二叉树,所以N1要么是0那么是1

2n = 2N0 - 1 + N1

又因为奇偶数,所以N1必为1,选A

例题2:

因为如果一个完全二叉树高度为h,则它的结点数在[2^(h-1) ,2^h -1 ] 

所以是B

例题3:

767 = 2N0 -1 + N1 

所以N1为0,N0为384,选B

4.二叉树的存储结构

二叉树一般可以使用两种结构储存,顺序结构、链式结构 

4.1 顺序结构

顺序结构存储时使用数组来存储适合表示满二叉树和完全二叉树,因为完全二叉树不会有空间的浪费顺序结构在物理逻辑上时一个数组,在逻辑结构上是一棵二叉树。

规定根节点在数组内下标是0,根据满二叉树性质,我们可以推导出父子节点在数组中的下标关系

  • parent = (child - 1)/  2
  • leftchild  = parent *2 +1
  • rightchild = parent*2 + 2

4.2 链式结构 

如果是非完全二叉树,那么顺序存储就不适合了,因为会造成空间的浪费,空间利用率不高,所以数组存储表示二叉树只适合完全二叉树

 三、二叉树的顺序结构及实现 

        普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

注意:堆在物理逻辑上时一个数组,在逻辑结构上是一棵二叉树。

1.堆的性质

  • 堆中某个节点的值总小于等于或大于等于其父节点的值
  • 堆是一颗完全二叉树
  • 所有父节点大于等于孩子的堆称为大根堆,反之所有父节点都小于等于孩子的堆称为小根堆

2.堆的插入

        根据堆是大根堆或小根堆,来选择向上调整或向下调整,向堆内插入数据,应按照数组储存顺序挨个插入,在插入后,要判断其与父节点的大小关系,选择向上调整(大根堆)或向下调整(小根堆),直到满足条件为止

3.堆的实现

堆的结构体

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

 HeapInit 初始化

void HeapInit(HP* php)
{assert(php);php->a = (HPDataType*)malloc(sizeof(HPDataType) * 4);if (php->a == NULL){perror("malloc fail");return;}php->size = 0;php->capacity = 4;
}

 HeapPush 插入

  • 插入后需要和父节点比较大小,进行向上调整或向下调整,所以单独封装一个函数实现该功能
  • 判断要插入时,size与capacity的值是否相等,进而判断是否要扩容(在顺序表和栈中我们都学过)
  • 最坏情况为新插入的数据最大,一直交换到根节点
void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType x = *p1;*p1 = *p2;*p2 = x;
}// 除了child这个位置,前面数据构成堆
void AdjustUp(HPDataType* a, int child)
{int parent = (child - 1) / 2;//父节点就这样算//while (parent >= 0) 当child = 0的时候,parent计算之后也是0,还会进入循环,这是错误的,但是又因为if条件不满足,所以break跳出循环了,这就是著名的虽然错误但是莫名其妙跑起来了while (child > 0){if (a[child] > a[parent]){Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}void HeapPush(HP* php, HPDataType x)
{assert(php);if (php->size == php->capacity){HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * php->capacity * 2);if (tmp == NULL){perror("realloc fail");return;}php->a = tmp;php->capacity *= 2;}php->a[php->size] = x;php->size++;AdjustUp(php->a, php->size - 1);//向上调整,因为size++了,所以size-1
}

 HeapPop 删除

  • 我们要考虑有意义的删除,即如果删除尾部数据,堆并没有什么实际的作用,而如果我们删除堆顶数据,那么我们可以得到第二大或第二小的数据,这在现实生活中是有意义的,因为生活中有许多排序,找到排名前k的数据,这是一个有实际需求的功能,所以,我们删除堆顶元素
  • 要删除堆顶数据的话,不能挪动删除(将后面的数据前移),因为这样搞不仅效率低,而且搞完之后父子兄弟关系全都会乱最优方法是,将堆顶数据与最后一个数据进行交换,再删除最后一个数据,这样既不会使父子兄弟间关系混乱,也能做到有实际意义
  • 与会后一个数据交换后,堆的结构已经发生改变,我们要恢复堆的结构,将堆顶元素向下调整即与它的两个孩子中最大的孩子比较大小,进而进行交换调整,最坏情况到叶子节点
  • 在向下调整函数中,会出现数组越界风险,在循环时要加以判断


// 左右子树都是大堆/小堆
void AdjustDown(HPDataType* a, int n, int parent)
{int child = parent * 2 + 1;while (child < n){// 选出左右孩子中大的那一个//这里有小问题,该点是否有右孩子,如果没有那么child+1就越界了,所以要先判断右孩子是否存在,并且还要注意逻辑判断不能写反if (child + 1 < n && a[child + 1] > a[child]){++child;}if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}void HeapPop(HP* php)//删除要删堆顶数据,因为删尾没有什么意义//删了堆顶,那么第二大或第二小的数据就会显现出来,这对于现实top k问题都是有意义的
{assert(php);assert(!HeapEmpty(php));// 删除数据Swap(&php->a[0], &php->a[php->size - 1]);php->size--;//size--,减小有效范围AdjustDown(php->a, php->size, 0);
}

HeapTop 堆顶元素 

HPDataType HeapTop(HP* php)
{assert(php);return php->a[0];
}

 HeapEmpty 判空函数

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

HeapSize 数据个数

int HeapSize(HP* php)
{assert(php);return php->size;
}

4.堆的代码 

Heap.h

#pragma once#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>// 
typedef int HPDataType;
typedef struct Heap
{HPDataType* a;int size;int capacity;
}HP;void HeapInit(HP* php);
void HeapDestroy(HP* php);void HeapPush(HP* php, HPDataType x);
void HeapPop(HP* php);
HPDataType HeapTop(HP* php);
bool HeapEmpty(HP* php);
int HeapSize(HP* php);void AdjustUp(HPDataType* a, int child);
void AdjustDown(HPDataType* a, int n, int parent);

Heap.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"void HeapInit(HP* php)
{assert(php);php->a = (HPDataType*)malloc(sizeof(HPDataType) * 4);if (php->a == NULL){perror("malloc fail");return;}php->size = 0;php->capacity = 4;
}void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType x = *p1;*p1 = *p2;*p2 = x;
}// 除了child这个位置,前面数据构成堆
void AdjustUp(HPDataType* a, int child)
{int parent = (child - 1) / 2;//父节点就这样算//while (parent >= 0) 当child = 0的时候,parent计算之后也是0,还会进入循环,这是错误的,但是又因为if条件不满足,所以break跳出循环了,这就是著名的虽然错误但是莫名其妙跑起来了while (child > 0){if (a[child] > a[parent]){Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}void HeapPush(HP* php, HPDataType x)
{assert(php);if (php->size == php->capacity){HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * php->capacity * 2);if (tmp == NULL){perror("realloc fail");return;}php->a = tmp;php->capacity *= 2;}php->a[php->size] = x;php->size++;AdjustUp(php->a, php->size - 1);//向上调整,因为size++了,所以size-1
}// 左右子树都是大堆/小堆
void AdjustDown(HPDataType* a, int n, int parent)
{int child = parent * 2 + 1;while (child < n){// 选出左右孩子中大的那一个//这里有小问题,该点是否有右孩子,如果没有那么child+1就越界了,所以要先判断右孩子是否存在,并且还要注意逻辑判断不能写反if (child + 1 < n && a[child + 1] > a[child]){++child;}if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;//先移动parentchild = parent * 2 + 1;}else{break;}}
}
//不能挪动删除,因为这样搞不仅效率低,而且搞完之后父子兄弟关系全都会乱
void HeapPop(HP* php)//删除要删堆顶数据,因为删尾没有什么意义//删了堆顶,那么第二大或第二小的数据就会显现出来,这对于现实top k问题都是有意义的
{assert(php);assert(!HeapEmpty(php));// 删除数据Swap(&php->a[0], &php->a[php->size - 1]);php->size--;AdjustDown(php->a, php->size, 0);
}HPDataType HeapTop(HP* php)
{assert(php);return php->a[0];
}bool HeapEmpty(HP* php)
{assert(php);return php->size == 0;
}int HeapSize(HP* php)
{assert(php);return php->size;
}

Test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"int main()
{HP hp;HeapInit(&hp);HeapPush(&hp, 4);HeapPush(&hp, 18);HeapPush(&hp, 42);HeapPush(&hp, 12);HeapPush(&hp, 21);HeapPush(&hp, 3);HeapPush(&hp, 5);HeapPush(&hp, 5);HeapPush(&hp, 50);HeapPush(&hp, 5);HeapPush(&hp, 5);HeapPush(&hp, 15);HeapPush(&hp, 5);HeapPush(&hp, 45);HeapPush(&hp, 5);int k = 0;scanf("%d", &k);while (!HeapEmpty(&hp) && k--){printf("%d ", HeapTop(&hp));HeapPop(&hp);}printf("\n");return 0;
}


总结

        本节简单学习了二叉树的概念及顺序存储堆的实现,下节将讲解如何使用堆排序,分析时间复杂度。

 最后,如果小帅的本文哪里有错误,还请大家指出,请在评论区留言(ps:抱大佬的腿),新手创作,实属不易,如果满意,还请给个免费的赞,三连也不是不可以(流口水幻想)嘿!那我们下期再见喽,拜拜!

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

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

相关文章

【C++进阶】继承、多态的详解(多态篇)

【C进阶】继承、多态的详解&#xff08;多态篇&#xff09; 目录 【C进阶】继承、多态的详解&#xff08;多态篇&#xff09;多态的概念多态的定义及实现多态的构成条件&#xff08;重点&#xff09;虚函数虚函数的重写&#xff08;覆盖、一种接口继承&#xff09;C11 override…

solr快速上手:聚合分组查询|嵌套分组指南(十二)

0. 引言 solr作为搜索引擎经常用于各类查询场景&#xff0c;我们之前讲解了solr的查询语法&#xff0c;而除了普通的查询语法&#xff0c;有时我们还需要实现聚合查询来统计一些指标&#xff0c;所以今天我们接着来查看solr的聚合查询语法 1. 常用聚合查询语法 以下演示我们…

面试题-React(一):React是什么?它的主要特点是什么?

探索React&#xff1a;前端开发中的重要角色与主要特点 引言&#xff1a; 在现代前端开发领域&#xff0c;React已经成为最受欢迎和广泛使用的JavaScript库之一。它由Facebook开发并于2013年首次发布。随着时间的推移&#xff0c;React在开发社区中获得了强大的支持和认可。本…

画质提升+带宽优化,小红书音视频团队端云结合超分落地实践

随着视频业务和短视频播放规模不断增长&#xff0c;小红书一直致力于研究&#xff1a;如何在保证提升用户体验质量的同时降低视频带宽成本&#xff1f; 在近日结束的音视频技术大会「LiveVideoStackCon 2023」上海站中&#xff0c;小红书音视频架构视频图像处理算法负责人剑寒向…

基于注意力神经网络的深度强化学习探索方法:ARiADNE

ARiADNE:A Reinforcement learning approach using Attention-based Deep Networks for Exploration 文章目录 ARiADNE:A Reinforcement learning approach using Attention-based Deep Networks for Exploration机器人自主探索(ARE)ARE的传统边界法非短视路径深度强化学习的方…

Python | Package | Python的三种包安装方式(pip/whl/tar.gz)

文章目录 PIP 安装与卸载Source 安装与卸载Whell 安装与卸载 PIP 安装与卸载 pip install xxx pip install xxxversion_numberpip install captcha pip install captcha0.4# XXX/anaconda3/envs/py373/lib/python3.7/site-packages pip uninstall captchaSource 安装与卸载 p…

C++音乐播放系统

C音乐播放系统 音乐的好处c发出声音乐谱与赫兹对照把歌打到c上 学习c的同学们都知道&#xff0c;c是一个一本正经的编程语言&#xff0c;因该没有人用它来做游戏、做病毒、做…做…做音乐播放系统吧&#xff01;&#xff01; 音乐的好处 提升情绪&#xff1a;音乐能够影响我们…

java语言B/S架构云HIS医院信息系统源码【springboot】

医院云HIS全称为基于云计算的医疗卫生信息系统( Cloud- Based Healthcare Information System)&#xff0c;是运用云计算、大数据、物联网等新兴信息技术&#xff0c;按照现代医疗卫生管理要求&#xff0c;在一定区域范围内以数字化形式提供医疗卫生行业数据收集、存储、传递、…

动手学深度学习--基础知识上篇

&#x1f388;动手学deep learning ☁️本专栏会定期更新关于动手学深度学习的每章知识点的讲解&#xff0c;题目答案 &#x1f47b;如果喜欢&#xff0c;欢迎点赞&#xff0c;收藏 动手学深度学习-预备知识篇 线性代数篇 1-3题讲解 证明一个矩阵 A \mathbf{A} A的转置的转置…

安卓手机跑 vins slam (2)

既然选择把vins的代码移植到新工程&#xff0c;那么就需要先确定自己电脑的Android Studio的C开发环节是OK的&#xff0c;可以通过创建C的示例工程&#xff0c;能正常跑通做验证。 选择Native C 需要选择用C哪个版本&#xff0c; 这里通过百度搜索&#xff0c;slam 编译需要C 1…

电脑提示丢失(或找不到)msvcp120.dll解决办法

msvcp140.dll是Microsoft Visual C Redistributable的一部分&#xff0c;它是Windows操作系统中的一个动态链接库文件。这个文件包含了许多C标准库函数的实现&#xff0c;对于一些依赖C标准库的应用程序来说&#xff0c;msvcp140.dll是非常重要的。msvcp140.dll的主要用途是提供…

热电联产在综合能源系统中的选址定容研究(matlab代码)

目录 1 主要内容 目标函数 程序模型 2 部分代码 3 程序结果 1 主要内容 该程序参考《热电联产在区域综合能源系统中的定容选址研究》&#xff0c;主要针对电热综合能源系统进行优化&#xff0c;确定热电联产机组的位置和容量&#xff0c;程序以33节点电网和17节点热网为例…

CI/CD入门(二)

CI/CD入门(二) 目录 CI/CD入门(二) 1、代码上线方案 1.1 早期手动部署代码1.2 合理化上线方案1.3 大型企业上线制度和流程1.4 php程序代码上线的具体方案1.5 Java程序代码上线的具体方案1.6 代码上线解决方案注意事项2、理解持续集成、持续交付、持续部署 2.1 持续集成2.2 持续…

小白到运维工程师自学之路 第七十五集 (Kubernetes 企业级高可用部署)2

8、添加master节点 在k8s-master2和k8s-master3节点创建文件夹 mkdir -p /etc/kubernetes/pki/etcd在k8s-master1节点执行 从k8s-master1复制密钥和相关文件到k8s-master2和k8s-master3 scp /etc/kubernetes/admin.conf root192.168.77.15:/etc/kubernetes scp /etc/kubernet…

UAF释放后重引用原理

原地址&#xff1a;https://blog.csdn.net/qq_31481187/article/details/73612451 原作者代码是基于linux系统的演示代码&#xff0c;因为windows和Linux 内存管理机制上略有不同&#xff0c;该程序在Windows需要稍微做些改动。 Windows上执行free释放malloc函数分配的内存后…

Docker碎碎念

docker和虚拟机的区别 虚拟机&#xff08;VM&#xff09;是通过在物理硬件上运行一个完整的操作系统来实现的。 每个虚拟机都有自己的内核、设备驱动程序和用户空间&#xff0c;它们是相互独立且完全隔离的。 虚拟机可以在不同的物理服务器之间迁移&#xff0c;因为它们是以整…

淘宝搜索店铺列表API:关键字搜索店铺信息 获取店铺主页 店铺所在地 服务评级

接口名称&#xff1a;item_search_seller 基本功能介绍 该API可以通过传入关键字&#xff0c;获取到淘宝商城的店铺列表&#xff0c;支持翻页显示。指定参数page获取到指定页的数据。返回的店铺信息包括&#xff1a;店铺名、店铺ID、店铺主页、宝贝图片、掌柜名字、店铺所在地…

idea2023 springboot2.7.5+mybatisplus3.5.2+jsp 初学单表增删改查

创建项目 修改pom.xml 为2.7.5 引入mybatisplus 2.1 修改pom.xml <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.2</version></dependency><!--mysq…

11. Vuepress2.x 关闭夜间模式

修改 docs/.vuepress/config.ts 配置文件 设置 themeConfig.darkMode属性详见 官网 module.exports {host: localhost, // ipport: 8099, //端口号title: 我的技术站, // 设置网站标题description: 描述&#xff1a;我的技术站,base: /, //默认路径head: [// 设置 favor.ico&a…

聊聊看React和Vue的区别

Vue 更适合小项目&#xff0c;React 更适合大公司大项目&#xff1b; Vue 的学习成本较低&#xff0c;很容易上手&#xff0c;但项目质量不能保证...... 真的是这样吗&#xff1f;借助本篇文章&#xff0c;我们来从一些方面的比较来客观的去看这个问题。 论文档的丰富性 从两个…