她是军统美女特工,色诱汉奸一把好手!一件事之后竟......

一.前言

我们在上一篇里简单了解了什么是树,以及树的一种特殊结构——二叉树。而我们对二叉树息息相关的堆进行了简单的介绍。我们知道了堆是借助二叉树中完全二叉树来实现的。它实现了二叉树的顺序存储。但对于普通的二叉树来说,顺序存储会造成空间浪费。所以对于普通的二叉树来说,链式结构存储更有利。现在我们一起了解一下二叉树的链式存储。

二.二叉树的链式存储

二叉树的链式存储是借助两个指针分别执行其左右孩子来实现的(图a)。因为二叉树每一个节点的度都小于等于2,所以我们可以采取左孩子右孩子的方式(二叉链表)来表示二叉树。之前左孩子右兄弟的表示方式更适合普通的树。除了这种方式,我们还有另一种链式表示方式:三叉链表,那就是多加一个指向父亲的指针(图b)。但是相比第二种来说,第一种我们更为常用。

 

//二叉链表
typedef int BinaryTreeDataType;
typedef struct BinaryTreeNode
{BinaryTreeDataType val;struct BinaryTreeNode* _left;//指向左孩子struct BinaryTreeNode* _right;//指向右孩子
}BTNode;//三叉链表
typedef struct BinaryTreeNode
{BinaryTreeDataType val;struct BinaryTreeNode* _left;//指向左孩子struct BinaryTreeNode* _right;//指向右孩子struct BinaryTreeNode* _parent;//指向父亲
}BTNode;

 三.二叉树的遍历

二叉树的遍历有四种方式,分别为前序遍历、中序遍历、后序遍历以及层序遍历。

  1. 前序遍历(Preorder Traversal):先访问根节点,然后按照先左后右的顺序遍历左子树和右子树,即根-左子树-右子树的顺序。

  2. 中序遍历(Inorder Traversal):先按照先左后右的顺序遍历左子树,然后访问根节点,最后再遍历右子树,即左子树-根-右子树的顺序。

  3. 后序遍历(Postorder Traversal):先按照先左后右的顺序遍历左子树和右子树,然后再访问根节点,即左子树-右子树-根的顺序。

  4. 层序遍历(Level Order Traversal),从上到下逐层遍历树的节点。

3.1前序遍历 

前序遍历也叫前根遍历,二叉树的前序遍历的访问规则是:遇到根节点后,先访问根节点,然后访问根节点的左子树,在访问根节点的右子树。而每一次遇到左子树或者右子树时,就将其再分为根、左子树、右子树。直到遍历完该树。如下图所示,它的前序遍历结果是什么?

 根据上面给出的方法,我们先访问根节点1,然后访问其左子树,然后访问左子树的根2,在访问2的左子树,然后访问左子树的根3,然后访问3的左子树为空,然后右子树为空,在访问2的右子树为空,在访问1的右子树……。

所以根据分析最后的结果应该为:1、2、3、N、N、N、4、5、N、N、6、N、N。在实际的访问时我们是不必打印空的。但是这里为了理解清楚我们将空也打印上了。

我们理解了前序遍历之后,怎样转化成代码呢?我们仔细思考上面的分析过程,我们再前序遍历的过程中运用到了递归,所以我们再完成代码的时候也要利用递归的思想。

//前序遍历
void PreorderTraversal(BTNode* root)
{if (root == NULL){printf("N ");}//先访问根节点printf("%d ", root->val);//访问其左子树PreorderTraversal(root->_left);//访问其右子树PreorderTraversal(root->_left);
}

我们还没有了解二叉树的创建,所以我们手搓一个和上图一样的二叉树以此来调试我们的前序遍历代码。

//树节点的创建
BTNode* BuyNode(int x)
{BTNode* node = (BTNode*)malloc(sizeof(BTNode));if (node == NULL){perror("malloc");return;}node->val = x;node->_left = NULL;node->_right = NULL;return node;
}//二叉树的创建
BTNode* CreateBinaryTree()
{BTNode* node1 = BuyNode(1);BTNode* node2 = BuyNode(2);BTNode* node3 = BuyNode(3);BTNode* node4 = BuyNode(4);BTNode* node5 = BuyNode(5);BTNode* node6 = BuyNode(6);node1->_left = node2;node1->_right = node4;node2->_left = node3;node4->_left = node5;node4->_right = node6;return node1;
}

我们运行我们的前序遍历代码,结果与我们推导出来的结果是一样的。 

3.2中序遍历 

中序遍历也叫中根遍历,其遍历二叉树的规则是:遇到根时先不访问根,先访问其左子树,在访问根,最后访问其右子树。遇到子树之后,又将其分为左子树、根、右子树。直到将该树的所有节点遍历完。 我们继续对上面的树进行分析:

我们遇到根1之后,先不访问它,先访问它的左子树,遇到左子树的根2之后,先访问它的左子树3,然后先访问3的左子树空,再访问3,在访问3的右子树空。在访问2,在访问2的右子树空。在访问1,在访问1的右子树4,访问4的左子树5,先访问5的左子树空,在访问5,在访问5的右子树空,在访问4,在访问4的右子树6,先访问6的左子树空,在访问6,最后访问6的右子树空。 

根据上面的分析,中序遍历的结果为:N、3、N、2、N、1、N、5、N、4、N、6、N。

我们发现,该方法也使用了递归的思想,而且代码应该也和前序遍历差不多:

//中序遍历
void InorderTraversal(BTNode* root)
{if (root == NULL){printf("N ");return;}//访问其左子树InorderTraversal(root->_left);//先访问根节点printf("%d ", root->val);//访问其右子树InorderTraversal(root->_right);
}

我们运行代码,该结果与我们的分析是一致的。 

3.3后序遍历 

后序遍历也叫后根遍历,其遍历二叉树的顺序是遇到根之后先不访问,先访问其左子树,再访问其右子树,最后访问其根。其分析的思路和前序中序是一样的,我们简单看一下:

 我们遇到根1之后,先访问其左子树2,遇到2之后,我们先访问2的左子树3,遇到3之后我们先访问3的左子树空,在访问3的右子树空,再访问3。访问完之后回到2,访问2的右子树空,再访问1的右子树……。

根据上面的分析,后序遍历的结果为:N、N、3、N、2、N、N、5、N、N、6、4、1.

我们由上面前序中序得出,其代码只是调用指令的顺序改变了而已,后序也一样。

//后序遍历
void PostorderTraversal(BTNode* root)
{if (root == NULL){printf("N ");return;}//访问其左子树PostorderTraversal(root->_left);//访问其右子树PostorderTraversal(root->_right);//先访问根节点printf("%d ", root->val);
}

 结果与分析相同。

四.二叉树的节点个数 

我们之前实现堆是借助数组实现的,而数组想要直到元素的个数是非常简单的。但是我们现在对于普通的二叉树来说,要用链式结构存储,那么现在该怎么统计二叉树的节点个数呢?

二叉树是递归而来的,我们刚才实现的遍历方法也借助了递归的思想。所以我们在实现该方法时也可以借助递归的思想。

要求一个树的节点个数是不是可以看作其左子树的节点个数+右子树的节点个数+自己就是该树的节点个数。

这就是一个递归的思想,将一个树的节点个数问题,转化成它的左右子树的节点+自己。然后左右子树又可以看成根,继续分为左右子树的节点数+自己。一直这样分化下去。直到分到叶子节点(没有左右儿子)。

//二叉树节点个数
int GetBinaryTreeSize(BTNode* root)
{if (root == NULL){return 0;}return GetBinaryTreeSize(root->_left) + GetBinaryTreeSize(root->_right) + 1;
}

 五.二叉树叶子结点的个数

叶子节点是度为0的节点,也就是没有左右儿子的节点。我们实现该方法时依旧可以借助递归的思想。二叉树叶子结点的个数 = 左子树叶子结点的个数+右子树叶子结点的个数。而左右子树又可以分为左右子树,我们一直这样分解下去,直到遇到某一个节点的左右子树都为NULL,就说明该节点就是叶子节点,我们就返回1,如果遇到NULL节点,我们直接返回0.

//二叉树叶子结点的个数
int GetBinaryTreeLeafSize(BTNode* root)
{if (root == NULL){return 0;}if (root->_left == NULL && root->_right == NULL){return 1;}return GetBinaryTreeLeafSize(root->_left) + GetBinaryTreeLeafSize(root->_right);
}

为了便于理解该代码,我们借助代码画出递归展开图来清晰思路。

 

六.二叉树第k层节点的个数 

我们用k来表示我们当前所在的层数,假设我们要求第2层的节点个数,k=2时,我们在第一层,当k=1时,我们就来到了待求层。当k不等于1时,我们将根分为左右子树分别去求第k层的节点的个数。当k=1时,root就指向这待求层的一个节点,所以k = 1就代表有一个节点,返回1即可。

// 二叉树第k层结点个数
int GetBinaryTreeLevelKSize(BTNode* root, int k)
{if (root == NULL){return 0;}if (k == 1){return 1;}return GetBinaryTreeLevelKSize(root->_left, k - 1) + GetBinaryTreeLevelKSize(root->_right, k - 1);
}

七.二叉树查找值为x的节点

要在二叉树种查找某个值为x的节点,我们只需要遍历二叉树的节点,找到与x值有相等值的第一个节点就行,然后返回该节点的地址。如果节点与x不相等,那就先比较该节点的左孩子,再比较该节点的右孩子。

//二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BinaryTreeDataType x)
{if (root == NULL){return NULL;}//先跟根比较,相等直接返回根节点,不相等在于根的左右子树比较if (root->val == x){return root;}//如果根的左子树的根与x相等,就直接返回,不必在比较右子树了。BTNode* ret1 = BinaryTreeFind(root->_left, x);if (ret1){return ret1;}BTNode* ret2 = BinaryTreeFind(root->_right, x);if (ret2){return ret2;}//走到这里说明,左右子树都不和x相等,所以返回NULLreturn NULL;
}

我们发现,该方法和前序遍历有点类似,都是先访问根,左子树、右子树。

我们查找2这个节点是可以查到的。 

当我们查找二叉树中没有的数据时,就会返回NULL。 

八.二叉树的深度

二叉树的深度也就是二叉树的高度,其高度是由最高的那一个子树决定的。那么怎么求该树的高度的呢?

二叉树的深度可以表示为左右子树中高的+1。如下图,该树的深度就是右子树的高度3+1 = 4.

 当我们遍历到叶子节点是就说明已经到底了,此时的高度就是1。

//二叉树的高度
int GetBinaryTreeDepth(BTNode* root)
{//如果根为空,说明该树的高度为0if (root == NULL){return 0;}if (root->_left == NULL && root->_right == NULL){return 1;}//树的高度等于左右子树中高的+1int height1 = GetBinaryTreeDepth(root->_left);int height2 = GetBinaryTreeDepth(root->_right);return height1 > height2 ? height1 + 1 : height2 + 1;
}

 九.二叉树的创建

我们在创建二叉树的时候,通常会给出一个二叉树的前序表达形式,我们要根据该前序序列将二叉树构建出来。假如有一段序列是“abc##de#g##f###”,其中#表示空格,在二叉树中表示空树,跟我们上面打印的前序序列中的N是一样的。

已知的前序序列是储存在一个数组中的,我们需要访问该数组的数据作为二叉树结点的值。如果我们访问的不是空,那就创建节点,给该节点赋值,然后先创建该节点的左孩子,在创建该节点的右孩子。每个节点的创建过程是相同的。这里之所以要传pi指针,是为了形参的改变可以影响实参。如果传int的话,再该函数+1了,可以到了递归的函数中i的值还是最初的值。

因为该序列是前序,所以我们创建的方式也与前序代码的逻辑相似。

//二叉树的创建
BTNode* CreateBinaryTree(char* arr,int *pi)
{//遇到"#"就是空格,直接返回NULLif (arr[*pi] == '#'){(*pi)++;return NULL;}BTNode* node = (BTNode*)malloc(sizeof(BTNode));if (node == NULL){perror("malloc");exit(-1);}node->val = arr[(*pi)++];node->_left = CreateBinaryTree(arr, pi);node->_right = CreateBinaryTree(arr, pi);return node;
}

 

十.二叉树的销毁

二叉树的销毁非常简单,我们只需要一个一个节点的销毁即可。那我们采用哪种销毁方式呢?前序?中序?还是后序呢?答案是后序。如果采用前两种的销毁方式,都会导致找不到有些节点。所以我们在这里采取后序递归的方式来销毁二叉树。

//二叉树的销毁
void BinaryTreeDestroy(BTNode* root)
{if (root == NULL){return;}BinaryTreeDestroy(root->_left);//先销毁左子树BinaryTreeDestroy(root->_right);//再销毁右子树free(root);//最后消除根root = NULL;
}

十一.二叉树的层序遍历

二叉树的层序遍历就是根据上下层关系,从左到右依次遍历的方法。

比如上图:该树的层序遍历的结果就是1、2、4、3、5、6。 那我们要如何实现层序遍历呢?这里我们需要借助队列来实现。

当前已经创建好了一个空队列q,我们先将该树的根插入到队列中。 

然后我们取出队头的数据,从队列中删除它,打印该节点数据后,再将将它的左右孩子插入到队列中,插入时要先判断其是否为空,空则不插入。 

然后我们重复刚才的操作,先将队头数据取出,然后从队列中删除它,打印该节点数据,再将其左右孩子插入到队列中。 重复该操作,直到队列为空。

//二叉树的层序遍历
void LevelOrderTraversal(BTNode* root)
{if (root == NULL){return;}//先创建队列Queue q = { 0 };QueueInit(&q);//先将根节点插入到队列中QueuePush(&q, root);while (!QueueEmpty(&q)){//取出队头数据BTNode* front = GetQueueFront(&q);QueuePop(&q);printf("%d ", front->val);if (front->_left != NULL){QueuePush(&q, front->_left);}if (front->_right != NULL){QueuePush(&q, front->_right);}}QueueDestroy(&q);
}

 与我们的结论相同。

十二.判断是不是完全二叉树

判断一个数是不是完全二叉树之前,我们先要了解什么是完全二叉树?完全二叉树就是只有最后一层不满,最后一层节点从左到右连续。

形如上图的二叉树就是完全二叉树,注意下图的就是完全二叉树,因为它最后一层的节点从左到右并不连续。

注意:满二叉树是特殊的完全二叉树 

 我们发现,对于完全二叉树来说,空节点的位置一定是连续的,它只会出现在最后一层。且第一个空节点出现,后面就全是空节点。而对于非完全二叉树来说,其空节点的后面还有可能有非空节点。

知道了这个特点,我们就可以借助刚才的层序遍历的逻辑来判断是不是完全二叉树。判断的方法是:先将根节点插入到队列中,然后取出该节点,从队列中删除,然后将该节点的儿子节点也插入到队列中。与层序遍历不同是,在插入的过程中将空节点也插入进去。

我们是在边取边插,等到我们取到第一个空结点的时候,此时就不再插入了,判断队列中此时是否都是空节点。如果是的话,那该树就是完全二叉树,否则就不是。

//判断一个树是不是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{if (root == NULL){return false;}//先创建队列Queue q = { 0 };QueueInit(&q);//先将根节点插入到队列中QueuePush(&q, root);while (!QueueEmpty(&q)){//取出队头数据BTNode* front = GetQueueFront(&q);QueuePop(&q);//判断取到没有第一个空节点if (front == NULL){//取到第一个空节点之后,判断队列中是不是全都是空while (!QueueEmpty(&q)){if (GetQueueFront(&q)){return false;}QueuePop(&q);}//到了这里说明队列里全是空节点break;}QueuePush(&q, front->_left);QueuePush(&q, front->_right);}QueueDestroy(&q);return true;
}

完!

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

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

相关文章

贪心+背包

这道题比较坑的就是我们的对于相同截止时间的需要排个序&#xff0c;因为我们这个工作是有时间前后顺序的&#xff0c;我们如果不排序的话我们一些截止时间晚的工作就无法得到最优报酬 #include<bits/stdc.h> using namespace std;#define int long long int t; int n; c…

看板项目之vue代码分析

目录&#xff1a; Q1、vue项目怎么实现的输入localhost&#xff1a;8080就能自动跳到index页面Q2、组合饼状图如何实现Q3、vue项目如何实现环境的切换Q4、vue怎么实现vue里面去调用js文件里面的函数 Q1、vue项目怎么实现的输入localhost&#xff1a;8080就能自动跳到index页面 …

数据结构——串

语言&#xff1a;C语言软件&#xff1a;Visual Studio 2022笔记书籍&#xff1a;数据结构——用C语言描述如有错误&#xff0c;感谢指正。若有侵权请联系博主 一、串的基本概念 子串&#xff1a;串中任意连续的字符组成的子序列称为该串的子串。 主串&#xff1a;包含子串的串称…

做一个能和你互动玩耍的智能机器人之三

内容节选自英特尔的开源项目openbot的body目录下diy下的readme&#xff0c;这是一个组装和连线方式的说明文档&#xff0c;接线需要配合firmware固件使用&#xff0c;固件代码的接线柱是对应的。 body目录内部十分丰富&#xff0c;主要介绍了这个项目的背景和硬件以及如何让他…

【SQL 新手教程 4/20】关系模型 --索引

&#x1f497; 关系数据库建立在关系模型上⭐ 关系模型本质上就是若干个存储数据的二维表 记录 (Record)&#xff1a; 表的每一行称为记录&#xff08;Record&#xff09;&#xff0c;记录是一个逻辑意义上的数据 字段 (Column)&#xff1a;表的每一列称为字段&#xff08;Colu…

echarts实现在市级行政区点击县级行政区,显示单个县级行政区地图数据

因需兼容ie&#xff0c;此处所有变量声明都用var。如无需支持&#xff0c;可另做let修改。 这里以常州市为例,我们可以去阿里云提供的地理工具去截取地图json数据DataV.GeoAtlas地理小工具系列 点击所选区域&#xff0c;右侧会对应显示json数据&#xff0c;再次点击右侧红框内…

MySQL 索引相关基本概念

文章目录 前言一. B Tree 索引1. 概念2. 聚集索引/聚簇索引3. 辅助索引/二级索引4. 回表5. 联合索引/复合索引6. 覆盖索引 二. 哈希索引三. 全文索引 前言 InnoDB存储引擎支持以下几种常见索引&#xff1a;BTree索引&#xff0c;哈希索引&#xff0c;全文索引 一. B Tree 索引…

2024巴黎奥运会竟然用AI做这些?

人工智能将成为 2024 年巴黎奥运会的焦点&#xff0c;组织者于四月制定了《奥运会人工智能议程》&#xff0c;这是一个涵盖人工智能对奥运会未来影响的框架。 该议程体现了国际奥委会及其主要合作伙伴的承诺&#xff0c;确保在奥运会上使用人工智能来促进团结、提高可持续性并加…

从零到一使用 Ollama、Dify 和 Docker 构建 Llama 3.1 模型服务

本篇文章聊聊&#xff0c;如何使用 Ollama、Dify 和 Docker 来完成本地 Llama 3.1 模型服务的搭建。 如果你需要将 Ollama 官方不支持的模型运行起来&#xff0c;或者将新版本 llama.cpp 转换的模型运行起来&#xff0c;并且想更轻松的使用 Dify 构建 AI 应用&#xff0c;那么…

网络传输层——UDP与TCP

前言&#xff1a; 1.国际网络体系结构&#xff1a; OSI模型: open system interconnect 理论模型 1977 国际标准化组织 各种不同体系结构的计算机能在世界范围内互联成网。 应用层:要传输的数据信息&#xff0c;如文件传输&#xff0c;电子邮件等…

数据结构:队列(顺序存储和链式存储)

文章目录 1. 队列的概念和结构2. 队列的链式存储实现2.1 初始化2.2 判断队列是否为空2.3 入队列2.4 出队列2.5 取队头数据2.6 取队尾数据2.7 队列有效数据的个数2.8 打印队列数据2.9 销毁2.10 源代码 3. 队列的顺序存储实现(循环队列)3.1 初始化3.2 判断队列是否为空3.3 判断队…

【数据结构之C语言实现动态顺序表】

引 入: 在讲顺序表之前得先了解线性表是什么&#xff1f; 线性表是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构&#xff0c;常见的线性表&#xff1a;顺序表&#xff0c;链表&#xff0c;栈&#xff0c;队列&#xff0c;字符串…… 线性表…

Meta 发布地表最大、最强大模型 Llama 3.1

最近这一两周看到不少互联网公司都已经开始秋招提前批了。不同以往的是&#xff0c;当前职场环境已不再是那个双向奔赴时代了。求职者在变多&#xff0c;HC 在变少&#xff0c;岗位要求还更高了。 最近&#xff0c;我们又陆续整理了很多大厂的面试题&#xff0c;帮助一些球友解…

【iOS】暑期第一周——ZARA app仿写

目录 前言无限轮播图分栏控件和滚动视图自定义cell遇到的问题调整图标大小单元格附件视图设置 总结 前言 暑假学习的第一周任务是对ZARA app进行仿写&#xff0c;充分运用之前学习的Objective-C语言和UI控件。我在编写demo的过程中遇到了一些问题&#xff0c;特写该博客作为学习…

LLM与搜索推荐

重磅推荐专栏: 《大模型AIGC》 《课程大纲》 《知识星球》 本专栏致力于探索和讨论当今最前沿的技术趋势和应用领域,包括但不限于ChatGPT和Stable Diffusion等。我们将深入研究大型模型的开发和应用,以及与之相关的人工智能生成内容(AIGC)技术。通过深入的技术解析和实践经…

VScode连接服务器免密登录

1、生成 SSH 密钥对 打开终端并输入以下命令生成 SSH 密钥对&#xff1a; 直接搜索 cmd&#xff0c;然后输入&#xff1a; ssh-keygen -t rsa -b 4096 一直回车就好了 这时公钥存储在/Users/你的用户名/.ssh/id_rsa.pub文件里&#xff0c;私钥存储在/Users/你的用户名/.ss…

简单的数据结构:栈

1.栈的基本概念 1.1栈的定义 栈是一种线性表&#xff0c;只能在一端进行数据的插入或删除&#xff0c;可以用数组或链表来实现&#xff0c;这里以数组为例进行说明 栈顶 &#xff1a;数据出入的那一端&#xff0c;通常用Top表示 栈底 :相对于栈顶的另一端&#xff0c;也是固…

黑马头条vue2.0项目实战(一)——项目初始化

1. 图标素材&#xff08;iconfont简介&#xff09; 制作字体图标的工具有很多&#xff0c;推荐使用&#xff1a;iconfont-阿里巴巴矢量图标库。 注册账户 创建项目 可以根据项目自定义 class 前缀 上传图标到项目 生成链接&#xff0c;复制 css 代码&#xff0c;在项目中使用…

【知识梳理】Shell的变量计算

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 Shell中有很多变量的计算&#xff0c;会用到多种运算符。例如这几种&#xff1a; 1. Shell中常见的算术运算符 运算符意义&…

github-page静态网页将字符串写入github库中文本文档

🏆本文收录于《CSDN问答解惑-专业版》专栏,主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!! 问题描述 github-page静态…