数据结构——二叉树的基本应用

在此之前我们已经初步了解了二叉树,在介绍堆的基本应用时,我们已经具体介绍了完全二叉树的基本应用,本章我们介绍二叉树的基本应用,这个不止指的是完全二叉树,而是指泛型的二叉树。

二叉树的基本应用,由于其左右子树层层连接的特性,大多都是由双向递归实现的,在本章我们要大量使用递归思想,希望能帮助到你

目录

二叉树的遍历

1.前序遍历(深度优先遍历DFS)

2.中序遍历

3.后序遍历

二叉树遍历的代码实现

1.实现前序遍历        

2. 实现中序遍历

3.实现后序遍历 

二叉树的基本函数接口

二叉树函数实现

1.二叉树结构体

2.通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树

3. 二叉树销毁

4.二叉树节点个数

5.二叉树叶子节点个数

6. 二叉树第k层节点个数

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

层序遍历(广度优先遍历BFS)

判断完全二叉树

总结:


二叉树的遍历

我们在介绍二叉树基本函数之前,先来介绍一下二叉树的遍历

二叉树的基本遍历分为三种,前序遍历,中序遍历,后序遍历

1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。

访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。

我们下面用这棵树进行讲解

1.前序遍历(深度优先遍历DFS)

即根据 根节点->左子树->右子树的顺序进行遍历,遇到叶结点即返回

逻辑思路:按照根节点 ->左子树->右子树的顺序我们可以排序,第一个就是根节点 1 ,之后去找左子树,再按照顺序可得 节点 2 ,再找2的左子树为节点 3 ,再去找3的子树,此时发现3的左右子树都为NULL,则返回到2节点,此时2节点的左子树已经找完,按照顺序,我们该去找右子树,而2的右子树为NULL,此时2节点已经找完,返回到1,1的左子树找完,去到1的右子树4节点,取根节点4,再找左子树5以及右子树6;

完成这个顺序就完成了前序遍历

前序遍历结果:1 2 3 4 5 6

2.中序遍历

即根据 左子树->根节点->右子树的顺序进行遍历,遇到叶结点返回。

具体逻辑和前序遍历类似,只是访问顺序不同

中序遍历结果:3 2 1 5 4 6

3.后序遍历

即根据 左子树->右子树->根节点的顺序进行遍历,遇到叶结点返回

具体逻辑和前序遍历类似,只是访问顺序不同

后序遍历结果:3 2 5 6 4 1

下面是3种遍历的顺序示意图:

 

二叉树遍历的代码实现

1.实现前序遍历        

我们详细介绍前序遍历

后序遍历和中序遍历我们用流程图解释

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>typedef int BTDataType;typedef struct BinaryTreeNode    //定义二叉树结构体
{struct BinaryTreeNode* left;struct BinaryTreeNode* right;BTDataType data;
}BTNode;BTNode* BuyBTNode(BTDataType x)    //创建二叉树节点函数
{BTNode* node = (BTNode*)malloc(sizeof(BTNode));if (node == NULL){printf("malloc fail\n");exit(-1);}node->data = x;node->left = node->right = NULL;return node;
}BTNode* CreatBinaryTree()    //手动创建一个如上图所示的二叉树
{BTNode* node1 = BuyBTNode(1);BTNode* node2 = BuyBTNode(2);BTNode* node3 = BuyBTNode(3);BTNode* node4 = BuyBTNode(4);BTNode* node5 = BuyBTNode(5);BTNode* node6 = BuyBTNode(6);node1->left = node2;node1->right = node4;node2->left = node3;node4->left = node5;node4->right = node6;return node1;
}void PrevOrder(BTNode* root) {    //先序遍历函数if (root == NULL) {printf("NULL ");return;}printf("%d ", root->data);PrevOrder(root->left);PrevOrder(root->right);
}

以下具体介绍二叉树前序遍历的函数调用,前序遍历将会调用13个栈帧

栈帧1:将根节点传入到prevOrder函数中,打印数据1,然后分别调用1左子树的prevOrder,再调用1右子树的prevOrder

栈帧2:打印2后,分别调用2左子树的prevOrder,再调用2右子树prevOrder

栈帧3:打印3后,分别调用3左子树的prevOrder,再调用3右子树prevOrder

栈帧4:由于3的左子树为NULL,那么就打印NULL,递归回上一层函数,到3右子树prevOrder,同时销毁栈帧4

栈帧5:由于3的右子树为NULL,那么就打印NULL,递归回上一层函数,到2右子树prevOrder,同时栈帧销毁5,3

栈帧6:栈帧6调用的是2的右子树,2的右子树为NULL,就打印NULL,递归回上一层函数,此时最初的根节点1的左子树全部访问完成,进而将去访问1的右子树,这里栈帧2结束,被销毁

栈帧7:打印4后,分别调用4左子树的prevOrder,再调用4右子树prevOrder

栈帧8:打印5后,分别调用5左子树的prevOrder,再调用5右子树prevOrder

栈帧9:由于5的左子树为NULL,那么就打印NULL,递归回上一层函数,到5右子树prevOrder,同时栈帧销毁9

栈帧10:栈帧10调用的是5的右子树,5的右子树为NULL,就打印NULL,递归回上一层函数,这里5节点已经完全访问完成,销毁栈帧10,8

栈帧11:此时访问完成4的左子树,去访问4的右子树,即打印6,然后分别调用6左子树的prevOrder,再调用6右子树prevOrder

栈帧12:栈帧12调用的是6的左子树,6的右子树为NULL,就打印NULL,递归回上一层函数,销毁栈帧12

栈帧13:此时访问完成6的左子树,去访问6的右子树,6的右子树为NULL,即打印NULL,至此已经将二叉树全部访问完成,依次返回结果,然后逐步将栈帧销毁。

下面是函数递归展开图能够更好的理解

2. 实现中序遍历

// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}BinaryTreeInOrder(root->_left);printf("%d ", root->_data);BinaryTreeInOrder(root->_right);
}

 递归展开图如下:

3.实现后序遍历 

void BinaryTreePostOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}BinaryTreePostOrder(root->_left);BinaryTreePostOrder(root->_right);printf("%d ", root->_data);
}

 递归展开图如下: 

 

二叉树的基本函数接口

下面我们实现二叉树的基本函数

先来看一下二叉树的函数接口

typedef char BTDataType;typedef struct BinaryTreeNode
{BTDataType _data;struct BinaryTreeNode* _left;struct BinaryTreeNode* _right;
}BTNode;// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi);
// 二叉树销毁
void BinaryTreeDestory(BTNode** root);
// 二叉树节点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root);
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root);
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root);
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root);
// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root);

二叉树函数实现

1.二叉树结构体

我们在之前已经介绍了二叉树节点的基本结构,包含三个元素:数据,左子树,右子树

这样我们可以清楚的访问到树的全部节点

结构体构建

typedef char BTDataType;typedef struct BinaryTreeNode
{BTDataType _data;struct BinaryTreeNode* _left;struct BinaryTreeNode* _right;
}BTNode;

2.通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树

这个函数的意思就是根据前序遍历的数组,来创建一个二叉树,我们既然知道了二叉树的前序遍历结果,同时空节点由#代替,我们就能利用递归来构建这个符合要求的二叉树、

进行遍历数组,由于这里是递归函数,那么我们就需要记住数组下标,而如果仅仅是用int类型的话,那么形参是会在递归过程中不断的销毁重建,是没法达到记住数组下标的目的的。

因此,我们传入int*,将地址传入,这样无论递归多少层,修改的变量一直是同一个变量,就能实现记录下标的目的

若数组元素为#则,返回NULL,同时下标+1

下面按照前序遍历进行递归构建二叉树

代码如下:

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int* pi)
{if (a[*pi] == '#'){(*pi)++;return NULL;}BTNode* root = (BTNode*)malloc(sizeof(BTNode));root->_data = a[(*pi)++];root->_left = BinaryTreeCreate(a, pi);root->_right = BinaryTreeCreate(a, pi);return root;}

3. 二叉树销毁

二叉树销毁的过程,也是一个递归的过程,但是我们不能用前序遍历进行递归,如果我们用前序遍历进行递归的话,先将root释放,那我们就没法找到根的左右子树了,因此我们需要用后序遍历进行销毁。

我们用递归,找到根的左子树和右子树,从叶节点开始,从叶到根依次释放掉根节点的空间即可

不要忘记递归的终止条件,当root == NULL 时 返回

这里我们需要传入结构体二级指针,因为我们的根节点是一级指针,因此想要释放,就需要传入其地址,由二级指针接收

代码如下:

// 二叉树销毁
void BinaryTreeDestory(BTNode** root)
{if (*root == NULL)return;BinaryTreeDestory((*root)->_left);BinaryTreeDestory((*root)->_right);free(*root);
}

4.二叉树节点个数

树的节点个数,也不难理解,还是利用递归,我们定义一个全局变量size,在左右子树进行递归,每次调用一次函数进行判断,若根节点不为NULL,就将size++,层层递归,最后size就是节点个数的结果

代码如下:

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

5.二叉树叶子节点个数

叶节点和节点个数略有差异,总体思想都是递归,但是叶结点需要进行判断,很显然叶结点的左右子树都为空,因此我们只要进行判断一下,符合条件就返回1,利用递归将左右子树的叶节点加起来就可以了

代码如下:

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

6. 二叉树第k层节点个数

统计k层的节点个数,还是利用递归,我们创建一个全局变量sizek,假设求第k层的节点,那么从第一层开始进行递归,每一次传入k = k -1,

当k==1 时sizek++

若k >1 则说明在k层之上,我们就需要继续递归,将 k -1,找到第k层

若k<1 说明已经找到了k层的元素,超过了第k层,直接返回0就可以了

注意,记住判断根节点是否为空,若为空就返回 0 ,不然会出现越界现象

逻辑图如下:

代码实现如下:

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

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

查找对应值的节点,还是利用递归思想,由根->左子树->右子树的顺序即前序遍历的顺序进行查找,找到对应值就返回对应节点,若没有找到就返回NULL。

注意:在递归查找对应节点时,我们需要用指针进行接收结果,然后先判断左子树结果是否为NULL,若不为NULL,就直接返回对应节点,这样就不用再去遍历右子树,节省了时间和空间

代码如下:

// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{if (root == NULL){return NULL;}if (root->_data == x)return root;BTNode* ret1 = BinaryTreeFind(root->_left, x);if (ret1)return ret1;BTNode* ret2 = BinaryTreeFind(root->_right, x);if (ret2)return ret2;return NULL;
}

层序遍历(广度优先遍历BFS)

层序遍历比较特殊,我们单独分一部分来讲

这里我们需要用到队列的基本用法,如果忘记了可以去复习一下

层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根结点所在层数为1,层序遍历就是从所在二叉树的根结点出发,首先访问第一层的树根结点,然后从左到右访问第2层上的结点,接着是第三层的结点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。

我们利用队列,上一层带下一层,将数据一层一层的入队列,根据队列的先进先出的特性,我们能将数据按照层序打印出来

逻辑图如下

我们建立队列q,此时队列q存的数据类型是结构体指针,这样才能记录根节点的地址,从而进行修改,然后判断根节点是否为空,若不为空就让根节点入队列,然后建立循环,当队列为空时说明数据已经遍历完成,此时终止循环

在循环中,我们记录队首数据,然后打印数据,删除队首数据,再用递归访问根节点的左右子树,从而实现层序遍历

代码如下:

void BinaryTreeLevelOrder(BTNode* root)
{Queue q;QueueInit(&q);if (root){QueuePush(&q, root);}while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);printf("%d ", front->_data);if (front->_left)QueuePush(&q, front->_left);if (front->_right)QueuePush(&q, front->_right);}
}

判断完全二叉树

判断完全二叉树,我们先来分析一下

完全二叉树:前n-1层 都是满的 第n层从左到右是连续的

我们仍然利用层序遍历的思想,不同的是,刚才我们的层序遍历不进空节点,这里我们也进入空节点 

1.进行层序遍历,空也进队列

2.遇到第一个空节点时,开始判断,后面全空就是完全二叉树,后面有非空就不是完全二叉树

 我们用逻辑图分析一下,即

下面我们根据思路来实现代码,总体思路还是层序遍历,但是不用打印数据,直接将左右子树数据入队,然后若出队数据为NULL节点,那么就进行判断,判断队列内是否全为NULL节点,若全为NULL节点,那么就是完全二叉树,若有非空数据,就不是完全二叉树。

代码实现如下:

// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root)
{Queue q;QueueInit(&q);if (root){QueuePush(&q, root);}while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);if (front == NULL){break;}QueuePush(&q, front->_left);QueuePush(&q, front->_right);}while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);if (front){QueueDestroy(&q);return false;}}QueueDestroy(&q);return true;
}

总结

二叉树的基本应用,还是比较复杂的,大多要使用递归实现,个别还需要用到队列的知识,因此我们需要掌握递归的原理,若不理解递归过程,可以通过画递归展开图的方式去理解,一定要将每一个函数接口给理解清楚,此类问题需要我们不断的练习和复习,才能将二叉树的知识巩固住,希望本篇文章能够帮助到你。

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

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

相关文章

刷代码随想录有感(81):贪心算法——分发饼干

题干&#xff1a; class Solution { public:int findContentChildren(vector<int>& g, vector<int>& s) {sort(g.begin(), g.end());sort(s.begin(), s.end());int index s.size() - 1;int res 0;for(int i g.size() - 1; i > 0; i--){if(index >…

GitLab项目中添加用户,并设置其角色权限等

注意&#xff1a;创建用户(new user)&#xff0c;创建完用户然后再项目邀请用户&#xff0c;选择创建过的用户 一、以管理员身份登录GitLab的WebUI并创建用户 1>.使用管理员登录GitLab 使用管理员(root)用户登录成功后&#xff0c;点击如下图所示的小扳手&#xff0c;点击…

C++数据结构之:链List

摘要&#xff1a; it人员无论是使用哪种高级语言开发东东&#xff0c;想要更高效有层次的开发程序的话都躲不开三件套&#xff1a;数据结构&#xff0c;算法和设计模式。数据结构是相互之间存在一种或多种特定关系的数据元素的集合&#xff0c;即带“结构”的数据元素的集合&am…

在HTML和CSS当中运用显示隐藏

1.显示与隐藏 盒子显示:display:block;盒子隐藏: display:none:隐藏该元素并且该元素所占的空间也不存在了。 visibility:hidden:隐藏该元素但是该元素所占的内存空间还存在&#xff0c;即“隐身效果”。 2.圆角边框 在CSS2中添加圆角&#xff0c;我们不得不使用背景图像&am…

学习笔记——数据通信基础——数据通信网络(网络工程师)

网络工程师 网络工程&#xff0c;就是围绕着网络进行的一系列的活动&#xff0c;包括∶网络规划、设计、实施、调试、排错等。网络工程设计的知识领域很宽广&#xff0c;其中路由和交换是计算机网络的基本。 网络工程师∶是在网络工程领域&#xff0c;掌握专业的网络技术&…

散户如何参与期权交易?

期权就是股票&#xff0c;唯一区别标的物上证指数&#xff0c;会看大盘吧&#xff0c;期权交易两个方向认购做多&#xff0c;认沽做空&#xff0c;双向t0交易没了&#xff0c;期权交易跟期货一样&#xff0c;对的&#xff0c;玩的也是合约&#xff0c;唯一区别没有保证金不会爆…

127.0.0.1 和 localhost 以及 0.0.0.0 区别

之前用 nginx 的时候&#xff0c;发现用这几个 IP&#xff0c;都能正常访问到 nginx 的欢迎网页。一度认为这几个 IP 都是一样的。 但本质上还是有些区别的。 首先 localhost 就不叫 IP&#xff0c;它是一个域名&#xff0c;就跟 "baidu.com",是一个形式的东西&…

什么是Redis脑裂,如何解决呢

Redis 脑裂问题是指&#xff0c;在 Redis 哨兵模式或集群模式中&#xff0c;由于网络原因&#xff0c;导致主节点&#xff08;Master&#xff09;与哨兵&#xff08;Sentinel&#xff09;和从节点&#xff08;Slave&#xff09;的通讯中断&#xff0c;此时哨兵就会误以为主节点…

IdentiFace——多模态人脸识别系统,可捕捉从情绪到性别的所有信息及其潜力

1. 概述 面部识别系统的开发极大地推动了计算机视觉领域的发展。如今&#xff0c;人们正在积极开发多模态系统&#xff0c;将多种生物识别特征高效、有效地结合起来。 本文介绍了一种名为 IdentiFace 的多模态人脸识别系统。该系统利用基于 VGG-16 架构的模型&#xff0c;将人…

【NumPy】NumPy线性代数模块详解:掌握numpy.linalg的核心功能

&#x1f9d1; 博主简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

Hexo最新实战:(一)Hexo7.0+GitHub Pages博客搭建

前言 很多平台都能写博客还有创作激励&#xff0c;为什么我又要搭一个&#xff1f;为什么这次要选择用Hexo框架&#xff1f; 对应的原因是流量自由和省钱&#xff0c;第一个&#xff0c;很多平台能写但不是都有收益&#xff0c;而且平台有自身的规则&#xff0c;比如会屏蔽一…

【区块链】外部应用程序与区块链进行交互

一&#xff0c;外部应用程序与区块链进行交互案例目标与流程 1.1案例目标 掌握FISCO BCOS应用环境的搭建 与使用&#xff08;FISCO BCOSWeBASE&#xff09;掌握基于Java SpringBoot的应 用程序后端项目搭建与开发。掌握应用程序后端与FISCO BCOS 链的交互。掌握应用程序前端…

WPF hc:PropertyGrid 嵌套显示

重点&#xff1a; 编写Edit特性即可&#xff1a; public class ParameterEditor : PropertyEditorBase{public override FrameworkElement CreateElement(PropertyItem propertyItem){var pg new PropertyGrid();return pg;}public override DependencyProperty GetDependen…

基于Vue+SpirngBoot的博客管理平台的设计与实现(论文+源码)_kaic

摘 要 随着当下社会的发展&#xff0c;互联网已经成为时代的主流&#xff0c;从此进入了互联网时代&#xff0c;对大部分人来说&#xff0c;互联网在日常生活中的应用是越来越频繁&#xff0c;大家都在互联网当中互相交流、学习、娱乐。博客正是扮演这样一个角色。博客已成为当…

实验八 单区域OSPF路由协议配置

一、实验目的 掌握 OSPF 动态路由协议的配置、诊断方法。 二、实验步骤 1、 运行Cisco Packet Tracer软件&#xff0c;在逻辑工作区放入三台路由器、两台工作站PC及一台笔记本&#xff0c;分别点击各路由器&#xff0c;打开其配置窗口&#xff0c;关闭电源&#xff0c;分别加…

Python装饰器的应用

Python 中的装饰器是一种语法糖&#xff0c;可以在运行时&#xff0c;动态的给函数或类添加功能。装饰器本质上是一个函数&#xff0c;使用 函数名就是可实现绑定给函数的第二个功能 。它的作用就是在不修改被装饰对象源代码和调用方式的前提下为被装饰对象添加额外的功能。 …

Ownips+Coze海外社媒数据分析实战指南

目录 一、引言二、ISP代理简介三、应用实践——基于Ownips和coze的社媒智能分析助手3.1、Twitter趋势数据采集3.1.1、Twitter趋势数据接口分析3.1.2、Ownips原生住宅ISP选取与配置3.1.3、数据采集 3.2、基于Ownips和Coze的社媒智能助手3.2.1、Ownips数据采集插件集成3.2.2、创建…

【Unity入门】认识Unity编辑器

Unity 是一个广泛应用于游戏开发的强大引擎&#xff0c;从 1.0 版本开始到现在&#xff0c;其编辑器的基本框架一直保持稳定。其基于组件架构的设计&#xff0c;使得界面使用起来直观且高效。为了更好地理解 Unity 的界面&#xff0c;我们可以将其比喻为搭建一个舞台。以下是对…

【AI+chat】推荐一款基于大模型的智能对话机器人,支持微信公众号、企业微信应用、飞书、钉钉接入

之前写了一篇文章&#xff0c; coze配置 kimichat集成到微信公众号聊天 【AIchat】手把手配置kimichat集成到微信公众号中对话聊天 。 有同学私信我有没有开源项目&#xff0c; 这里推荐一款chatgpt-on-wechat。 官方git地址&#xff1a;https://github.com/zhayujie/ch…

Yann LeCun 和 Elon Musk 就 AI 监管激烈交锋

&#x1f989; AI新闻 &#x1f680; Yann LeCun 和 Elon Musk 就 AI 监管激烈交锋 摘要&#xff1a;昨天&#xff0c;Yann LeCun 和Elon Musk 在社交媒体就人工智能的安全性和监管问题展开激烈辩论。LeCun 认为目前对 AI 的担忧和监管为时过早&#xff0c;主张开放和共享。而…