「数据结构」二叉树1

🎇个人主页:Ice_Sugar_7
🎇所属专栏:C++启航
🎇欢迎点赞收藏加关注哦!

文章目录

  • 🍉树
  • 🍉二叉树
    • 🍌特殊二叉树
    • 🍌二叉树的性质
    • 🍌存储结构
  • 🍉堆
    • 🍌堆的结构
    • 🍌插入
      • 🥝向上调整算法
        • 🫐时间复杂度分析
    • 🍌删除
      • 🥝向下调整算法
        • 🫐时间复杂度分析
    • 🍌堆的创建(堆的初始化)
    • 🍌堆排序
    • 🍌top k 问题
  • 🍉写在最后

🍉树

●树是一种非线性的数据结构,它是由n(n>=0)个结点组成,具有层次关系
●有一个特殊的结点,称为根结点,根节点没有前驱结点
●除根节点外,其余结点被分成M(M>0)个互不相交的集合,每个集合是一棵子树

🍉二叉树

二叉树一个非空结点的子树为空或者至多两个子树(左子树和右子树)
在这里插入图片描述
从这个图可以看出:

二叉树不存在度大于2的结点
二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树

🍌特殊二叉树

满二叉树每一层结点数都达到最大值的二叉树。如果一棵满二叉树有k层,那它结点总数就是2^k-1
完全二叉树最后一层抠掉几个结点的满二叉树,就是一般的完全二叉树(满二叉树是特殊的完全二叉树)

🍌二叉树的性质

二叉树的性质都在下图了:
在这里插入图片描述
在这里插入图片描述

🍌存储结构

二叉树一般使用两种结构存储:顺序结构链式结构
顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实使用中只有堆才会使用数组来存储
二叉树顺序存储在物理结构上是一个数组,在逻辑结构上是一颗二叉树
我们的代码是按照其物理结构写的,而具体想实现的函数接口则是根据逻辑结构展开的(往下看入堆、出堆、调整等函数之后,你就能理解这句话了)
在这里插入图片描述

本文讲二叉树的顺序存储结构——堆(正文开始)


🍉堆

堆分为两种:大堆和小堆。

大堆:除了叶子结点外,所有结点的孩子都比自己小
小堆:除了叶子结点外,所有结点的孩子都比自己大

根据堆的逻辑结构可知,大堆是上面的结点(位于较低层次的结点)大,小堆是上面的结点小

🍌堆的结构

堆的物理结构就是顺序表,所以代码基本和顺序表一模一样

typedef int HPDataType;
typedef struct Heap
{HPDataType* _a;int _size;int _capacity;
}Heap;

🍌插入

插入这一步很简单,就直接往数组插入元素(注意检查容量是否足够,并且插入后记得让size加1)
插入后,要对这个元素进行调整,采用向上调整

🥝向上调整算法

假设要建一个小堆,那就要拿它和它的双亲进行比较,如果它比双亲小,就和双亲交换位置。假设数组名为_a,大小为size,那插入的结点下标为size-1,它的双亲在数组中的下标就是(size-1-1)/2
这里要用循环,将插入的结点记为child,当child为0时(即它到了堆顶),循环终止。如果中途经比较后发现不用换位置的话,说明调整好了,直接break跳出循环
在这里插入图片描述
代码如下:

typedef int HPDataType;void Swap(HPDataType* hp1, HPDataType* hp2) {HPDataType tmp = *hp1;*hp1 = *hp2;*hp2 = tmp;
}//小堆向上调整
void AdjustUp(Heap* hp,int child) {assert(hp);int parent = (child - 1) / 2;while (child > 0) {if (hp->_a[child] >= hp->_a[parent]) //孩子比双亲大,退出循环break;else {Swap(&(hp->_a[child]),&(hp->_a[parent]));  //两结点交换child = parent;parent = (parent - 1) / 2;}}
}
🫐时间复杂度分析

因为堆是完全二叉树,而满二叉树也是完全二叉树,为了简化问题,就用满二叉树来证明了(时间复杂度本来看的就是近似值,多几个节点不影响最终结果),下面向下调整算法的时间复杂度也这样处理
假设有n个结点,那就有log(n+1)层,那每次向下调整最多遍历log(n+1)次,总共有n个结点,那么就遍历n*log(n+1)次,时间复杂度就是O(N*logN)


🍌删除

不能直接将数组往前挪一位,因为这样虽然在物理结构(数组)上没什么问题,但是在逻辑结构(完全二叉树)上就有问题了,会打乱结点间的关系(比如原先的兄弟现在变为父子,父子变兄弟)
有一个比较巧妙的解决办法,就是将根结点和尾结点(数组最后一个元素)交换位置,然后将新的尾结点删掉,这样就不会影响到结点间的关系了
删掉后要进行向下调整,这就涉及到向下调整算法了

🥝向下调整算法

现在有一个数组,它有n个元素,从逻辑结构上看成是完全二叉树,我们从根结点开始,通过向下调整算法可以把它调整为一个小堆
这种算法的前提是左右子树必须是一个堆,才能调整

int array[] = {27,15,19,18,28,34,65,49,25,37};

调整过程如图:
在这里插入图片描述
每次调整,先比较该结点两个孩子的大小现在要调整为小堆,就先找出较小的孩子,然后这个孩子和双亲进行比较,若孩子<双亲,就把它和双亲交换位置;反之则说明调整完毕

调整的过程显然也要用循环。我们将双亲结点记为parent,左孩子结点记为child(因为左右孩子下标相差1,没必要用leftchild和rightchild进行区分)。那右孩子就是child + 1,不过由于右孩子可能不存在(当child为叶子结点时可能会有这种情况),所以我们得在循环里面判断一下

这里采用假设法,就是我们先假设左孩子是较小的结点(因为右孩子可能不存在,不方便假设),如果右孩子存在的话,就拿左右孩子进行比较,最后将child赋给较小者
(假设法相比于if语句,可以有效简化代码,具体可以看我之前那篇判断相交链表的题解,or看下面的代码也ok)
文章链接:判断相交链表

那循环的终止条件呢?显然当child>=n的时候就要跳出循环了
代码如下:

void AdjustDown(HPDataType* a,int n,int parent) {  //n为数组大小assert(a);int child = 2 * parent + 1;  //左孩子下标while (child < n) {if (child + 1 < n && a[child+1] <= a[child]) {  //右结点存在并且右孩子比左孩子小child++;  //将child设为右孩子结点,等会儿拿它和双亲进行比较,决定是否交换}if (a[child] < a[parent]) {Swap(&a[child], &a[parent]);parent = child;  //更新双亲结点child = parent * 2 + 1;  //更新孩子结点}elsebreak;  //不用换位置说明调整完毕}
}
🫐时间复杂度分析

在这里插入图片描述
“向下移动”的层数指的是最多要调整几次,即从这个结点开始,一直调整到叶子结点为止(最坏的情况)
从结果可以看出:向下调整的时间复杂度(O(N))比向上调整的小,所以建议使用向下调整


🍌堆的创建(堆的初始化)

建堆既可以建一个空堆,也可以根据一个现成的数组建堆
建空堆就是将数组赋为空指针,然后size和capacity都赋为0。和顺序表初始化一样,不多赘述
这里主要来讲数组建堆
思路是:给堆开辟空间+拷贝+调整
●开空间:数组多大就开多大
●拷贝:使用memcpy将数组的元素拷贝给堆
●调整:向上调整or向下调整

先展示向上调整

void HeapCreate(Heap* hp, HPDataType* a, int n) {assert(hp);assert(a);HPDataType* tmp = (HPDataType*)malloc(sizeof(HPDataType) * n);if (tmp == NULL) {perror("malloc fail");exit(-1);}hp->_a = tmp;hp->_capacity = hp->_size = n;memcpy(hp->_a, a, n * sizeof(HPDataType));  //把数组数据拷贝到堆的数组中int parent = (hp->_size - 1) / 2;for (int i = 1; i < n; i++) {  //  调整建堆AdjustUp(hp, i);}
}

也可以复用刚才写的入堆函数,因为它自带向上调整函数。而且push函数是将数组的元素一个一个放进堆的,这样就不需要memcpy了,代码如下(为方便观察,我把向上调整函数和入堆函数也放在下面):

//小堆向上调整
void AdjustUp(Heap* hp, int child) {assert(hp);int parent = (child - 1) / 2;while (child > 0) {if (hp->_a[child] >= hp->_a[parent]) //孩子比双亲大,退出循环break;else {Swap(hp->_a[child], hp->_a[parent]);  //两结点交换child = parent;parent = (child - 1) / 2;}}
}void HeapPush(Heap* hp, HPDataType x) {assert(hp);//如果满了  那就要扩容if (hp->_capacity == hp->_size) {int newcapacity = hp->_capacity == 0 ? 4 : 2 * hp->_capacity;HPDataType* tmp = (HPDataType*)realloc(hp->_a, newcapacity * sizeof(HPDataType));if (tmp == NULL) {perror("realloc fail");exit(-1);}hp->_a = tmp;hp->_capacity = newcapacity;}hp->_a[hp->_size] = x;hp->_size++;AdjustUp(hp, hp->_size - 1);
}void HeapCreate(Heap* hp, HPDataType* a, int n) {assert(hp);HeapInit(hp);  //初始化为空堆for (int i = 0; i < n; ++i) {HeapPush(hp, a[i]);}
}

由于向上调整的效率不及向下调整,所以建议采用向下调整建堆
向下调整要求左子树和右子树也都是堆,又因为单个叶子结点既可以看作是大堆,也可以看成小堆,所以我们从叶子结点的双亲开始向下调整

比如下面这个数组要建一个大堆

int a[] = {4,3,5,7,2,6,8,65,100,70,32,50,60};

在这里插入图片描述
调整后,红色方框内就是一个大堆了,对于3,5这两个结点而言,左右子树都是大堆,那它们也可以向下调整了
代码如下:

void HeapCreate(Heap* hp, HPDataType* a, int n) {assert(hp);HPDataType* tmp = (HPDataType*)malloc(sizeof(HPDataType) * n);if (tmp == NULL) {perror("malloc fail");exit(-1);}hp->_a = tmp;hp->_capacity = hp->_size = n;memcpy(hp->_a, a, n * sizeof(HPDataType));  //把数组数据拷贝到堆的数组中int parent = (hp->_size - 1 - 1) / 2;  //最后一个结点的双亲结点for (int i = parent; i >= 0;i--) {     //从该结点开始进行向下调整AdjustDown(hp->_a, n, i);}
}

🍌堆排序

现在有一个数组,要把它排成升序
如果建小堆,那么很容易就可以找到最小的元素。但是要找次小元素的时候,把数组剩下的元素看作完全二叉树的话,它们之间的关系会乱掉

●所以要建大堆,建好后最大的元素就在根结点,将它和最后一个结点交换,就把最大的元素排好了
●然后size-1剔除最大的元素,对于剩下的元素,因为根结点的左右子树也都是大堆,可以采用向下调整,调整后可以把第二大的元素移动到堆顶(根结点),再和最后一个结点交换,第二大元素就排好了
●剩下的元素也如法炮制

void HeapSort(int* a, int k) {  //a为给定数组for (int i = (k - 1 - 1) / 2; i >= 0; i--) {   //调整为一个堆AdjustDown(a, k, i);}for (int i = k - 1; i >= 0; i--) {  //采用删除结点的思想,先交换,再调整Swap(a[0], a[i]);AdjustDown(a, i, 0);}
}

排序后得到:
在这里插入图片描述


🍌top k 问题

这个问题就是要找出数组中从大到小(或从小到大)的前k个数,下面以从大到小为例
如果要找从大到小的前k个数,我们可以先从数组中选k个数,建一个大小为k的小堆,然后将数组中剩下的数和堆顶的数进行比较,如果比它大,就替代它,然后向下调整。
这个方法的原理是:放一个比较小的数“卡”在堆顶,类似守门员,比它大的数就能进堆,不断把堆中较小的数踢出去,到最后就留下最大的前k个数

//取最大的前k个
void TopK(HPDataType* pa, int n, int k) {Heap ph;HeapInit(&ph);HeapCreate1(&ph, pa, k);  //建小堆for (int i = k; i < n; i++)  //遍历剩下的元素{if (pa[i] > ph._a[0]) {ph._a[0] = pa[i];AdjustDown(ph._a, k, 0);  //小堆向下调整}}HeapSort(ph._a, k);  //将得到前k数进行排序HeapPrint(&ph);
}

🍉写在最后

以上就是本篇文章的全部内容,如果你觉得本文对你有所帮助的话,那不妨点个小小的赞哦!(比心)

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

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

相关文章

【PHP】身份证正则验证、校验位验证

目录 1.正则 简单正则 详细正则 2.校验位验证 1.正则 简单正则 function isValidIdCardNumber($idCardNumber) {// 身份证号长度为 15 位或 18 位$pattern /^(?:\d{15}|\d{17}[\dxX])$/;return preg_match($pattern, $idCardNumber); }$idCardNumber 12345678901234567…

Linux中用户名与UID、用户组名与GID的关系(为什么有用户名、用户组了,还要搞个UID、GID?)

文章目录 Linux中用户名与UID、用户组名与GID的关系1. 用户名和UID的区别1.1 用户名1.2 用户ID (UID) 2. 用户组名和GID的区别2.1 用户组名2.2 组ID (GID) 3. 为什么需要UID和GID Linux中用户名与UID、用户组名与GID的关系 在Linux系统中&#xff0c;用户名和用户组名主要用于…

ffmpeg入门之Windows开发之二(视频转码)

添加ffmpeg windows编译安装及入门指南-CSDN博客 的头文件和依赖库如下&#xff1a; main 函数如下&#xff1a; extern "C" { #ifdef __cplusplus #define __STDC_CONSTANT_MACROS #endif } extern "C" { #include <libavutil/timestamp.h> #in…

OpenCV-8RGB和BGR颜色空间

一. RGB和BGR 最常见的色彩空间就是RGB&#xff0c;人眼也是基于RGB的色彩空间去分辨颜色。 OpenCV默认使用的是BGR. BGR和RGB色彩空间的区别在于图片在色彩通道上的排列顺序不同。 二.HSV, HSL和YUV 1.HSV(HSB) OpenCV用的最多的色彩空间是HSV. Hue&#xff1a;色相&…

LeetCode(Hot100)——7:数字反转

复习 &#xff08;1&#xff09;char charAt(int index) 返回指定索引处的 char 值。 &#xff08;2&#xff09;String substring(int beginIndex, int endIndex) 返回一个新字符串&#xff0c;它是此字符串的一个子字符串。 &#xff08;3&#xff09;String substring(int …

前端项目配置下载源npm, yarn,pnpm

前端项目配置下载源 npm: npm config set registry registryhttps://registry.npmmirror.com -g验证: npm config get registry yarn: yarn config set registry registryhttps://registry.npmmirror.com -gyarn config get registryyarn找不到, 需要管理员在命令行: set-exec…

【分享】5种方法将Excel设置为“只读”

将Excel表格设置以“只读方式”打开&#xff0c;可以提醒或者防止表格被随意改动&#xff0c;今天小编来分享一下将Excel设置为“只读”的5种方法。 方法一&#xff1a;通过“保护工作簿”设置 首先&#xff0c;打开Excel表格依次点击菜单选项卡【文件】→【信息】→【保护工作…

【无标题】CTF之SQLMAP

拿这一题来说 抓个包 复制报文 启动我们的sqlmap kali里边 sqlmap -r 文件路径 --dump --dbs 数据库 --tables 表

数据库操作习题12.12

考虑如下的人员数据&#xff0c;其中加下划线的是主码&#xff0c;数据库模式由四个关系组成: employee (empname, street, city) works (empname, compname, salary) company(id, compname, city) managers (empname, mgrname) 其中 关系 employee 给出人员的基本信息,包括人员…

浅析 SaaS、CRM、OA、ERP、eHR、进销存、财务系统的区别

在当今数字化时代&#xff0c;各种企业管理软件如雨后春笋般涌现&#xff0c;为企业的日常管理和运营带来了极大的便利。其中&#xff0c;SaaS、CRM、OA、ERP、eHR、进销存、财务系统是我们常常会听到的软件。这些软件各具特色&#xff0c;为企业解决了不同的问题。它们到底是什…

Armbian切换Wifi

找了个盒子刷了Armbian&#xff0c;主要有两个作用&#xff0c;一个是做离线下载和SMB。另外一个作用是用来搞打印服务器&#xff0c;因为家里角落比较多&#xff0c;因此有两个路由器信号&#xff0c;而打印机所在的位置的wifi信号&#xff0c;外网速度并不好&#xff0c;因此…

Relocations for this machine are not implemented,IDA版本过低导致生成汇编代码失败

目录 1、问题描述 2、安卓app发生崩溃&#xff0c;需要查看汇编代码上下文去辅助分析 3、使用IDA打开.so动态库文件&#xff0c;提示Relocations for this machine are not implemented 4、IDA版本较老&#xff0c;不支持ARM64的指令集&#xff0c;使用7.0版本就可以了 5、…

猫罐头避雷!猫咪罐头该如何挑选?

近年来&#xff0c;国货猫罐头在国内市场的崛起&#xff0c;真的是又喜又忧。喜的是国货的崛起打破了进口猫罐头一家独大的局面&#xff0c;忧的是担心大家可能会被网上大量的内容所迷惑&#xff0c;导致踩了不少坑。 那么面对市面上种类繁多的猫罐头&#xff0c;我们应该怎么…

linux系统和网络(一):文件IO

本文主要探讨linux系统编程的文件IO相关知识。 文件IO 文件存在块设备中为静态文件,open打开文件,内核在进程中建立打开文件的数据结构在内存中用于记录文件的文件参数,开辟一段内存用于存放内容,将静态文件转为动态文件 打开文件后对文件的读写操作都为对动态…

【最新版】在WSL上运行 Linux GUI (图形用户界面)应用(Gnome 文本编辑器、GIMP、Nautilus、VLC、X11 应用)

文章目录 一、 安装WSL0. 先决条件1. 全新安装2. 现有 WSL 安装3. 注意事项 二、运行 Linux GUI 应用1. 更新发行版中的包2. 安装 Gnome 文本编辑器启动 3. 安装 GIMP启动 4. 安装 Nautilus启动 5. 安装 VLC启动 6. 安装 X11 应用 适用于 Linux 的 Windows 子系统 (WSL) 现在支…

MATLAB求解微积分(代码+详细解读)

大多数实际工程问题常常简化为微分方程&#xff0c;其求解显地至关重要。 符号微积分 极限 % matlab提供的求极限函数limit(),其调用格式为 % y limit(fun,x,x0) % fun为要求解的函数&#xff0c;x为函数自变量&#xff0c;x0为函数自变量的取值&#xff0c;x趋近于x0 clc;…

windows环境下cmd找不到pip

报错信息&#xff1a;pip : 无法将“pip”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写&#xff0c;如果包括路径&#xff0c;请确保路径正确&#xff0c;然后再试一次。 所在位置 行:1 字符: 1 pip ~~~ CategoryInfo : ObjectNotFou…

DataX迁移MongoDB

DataX迁移MongoDB 项目地址&#xff1a;GitHub - alibaba/DataX: DataX是阿里云DataWorks数据集成的开源版本。迁移MongoDB&#xff0c;读取组件为mongodbreader&#xff0c;写入组件为mongodbwriter 源码修改 目前版本中&#xff0c;在迁移MongoDB时&#xff0c;若列的类型为…

7-12 sdut-Collection-sort--C~K的班级(II)(java for PTA)

经过不懈的努力&#xff0c;C~K终于当上了班主任。 现在他要统计班里学生的名单&#xff0c;但是C~K在教务系统中导出班级名单时出了问题&#xff0c;发现会有同学的信息重复&#xff0c;现在他想把重复的同学信息删掉&#xff0c;只保留一个&#xff0c; 但是工作量太大了&am…

Linux-----13、用户、组

# 用户、组 # 一、用户管理 # ㈠ 用户概念及作用(了解) **用户&#xff1a;**指的是Linux操作系统中用于管理系统或者服务的人 一问&#xff1a;管理系统到底在管理什么&#xff1f; 答&#xff1a;Linux下一切皆文件&#xff0c;所以用户管理的是相应的文件 二问&#x…