数据结构之二叉树的精讲

𝙉𝙞𝙘𝙚!!👏🏻‧✧̣̥̇‧✦👏🏻‧✧̣̥̇‧✦ 👏🏻‧✧̣̥̇:Solitary_walk

      ⸝⋆   ━━━┓
     - 个性标签 - :来于“云”的“羽球人”。 Talk is cheap. Show me the code
┗━━━━━━━  ➴ ⷯ

本人座右铭 :   欲达高峰,必忍其痛;欲戴王冠,必承其重。

👑💎💎👑💎💎👑 
💎💎💎自💎💎💎
💎💎💎信💎💎💎
👑💎💎 💎💎👑    希望在看完我的此篇博客后可以对你有帮助哟

👑👑💎💎💎👑👑   此外,希望各位大佬们在看完后,可以互相支持,蟹蟹!
👑👑👑💎👑👑👑


对二叉树的基本概念性的理解,若有不明白的友友们,可以看一下前期写的关于堆与二叉树的精讲

链接在此:

有了大家对二叉树的基本理解接下来,对以下知识的理解应该是易如反掌!

1.二叉树的链式结构的实现

 对于任何一个二叉树的节点来说:都是由值域,左孩子,右孩子构成,只不过对于某些节点而言左右孩子为空

1.1定义一个二叉树的结构体
typedef int BT_DataType;
typedef struct BinaryTreeNode
{struct BinaryTreeNode* left; //左孩子struct BinaryTreeNode* right;//右孩子BT_DataType val;  //值域}BT;
1.2二叉树的链式结构

 为了方便对二叉树的理解,我暂时手动创建节点,进行连接

BT* BT_Create()
{BT* n1 = BuyNode( 1);BT* n2 = BuyNode( 2);BT* n3 = BuyNode( 3);BT* n4 = BuyNode( 4);BT* n5 = BuyNode(5);BT* n6 = BuyNode( 6);BT* n7 = BuyNode( 7);BT* n8= BuyNode( 8);BT* n9 = BuyNode( 9);BT* n10 = BuyNode( 10);n1->left = n2;n1->right = n3;n2->right = n4;n3->left = n5;n3->right = n6;n2->left = n7;n4->left = n8;return n1;
}
2.二叉树的遍历
2.1前序遍历(先序遍历)

先访问根节点,其次访问左子树,左后访问右子树,注意这是一个递归的定义。

见图如下:

 代码:
void Pre_order(BT* root)
{if (root == NULL){printf("%c ", 'n');//返回n表示为空return;}printf("%d ", root->val);Pre_order(root->left);Pre_order(root->right);
}
递归展开图的解释:

2.2中序遍历

先访问左子树,在访问根节点最后访问右子树,依然是一个递归的定义

分析如下:
 代码:
void In_Order(BT* root)
{if (root == NULL){printf("%c ", 'n');//返回n表示为空return;}In_Order(root->left);printf("%d ", root->val);In_Order(root->right);
}
2.3后续遍历

先访问左子树,再访问右子树,最后访问根节点,依然是递归定义

分析见下:

代码:
void Post_Order(BT* root)
{if (root == NULL){printf("%c ", 'n');//返回n表示为空return;}Post_Order(root->left);Post_Order(root->right);printf("%d ", root->val);}
3.与二叉树相关节点的求解
3.1求二叉树所有节点个数

可能有些老铁们会说:直接进行计数就可以了:只有是当前节点不为空就让计数器(size)加一,采用前序遍历的方法。没错也可以这样但是这样有些坑需要注意一下否则一不小心就掉进坑里了。

 

 这时候可能有老铁会说,直接定义一个全局变量不就OK了,注意当我们连续调用BT_Size()这个函数进行求个数的话会有问题滴!

 因为szie作为一个全局变量,第一调用确实为8没有错,但是当我们后续在进行调用的时候就需要把size 手动进行置零,(关于这个问题详解,感兴趣的友友们,可以看前期我写的博客:链接在此,自取,蟹蟹支持!)要不然只会在当前size = 8 的基础上进行相加,这也就是为什么最后会出现16,24的这样结果了,也就不以为奇了。

代码:
int size = 0;
int BT_Size(BT* root)
{//int size = 0;if (root == NULL)return size;size++;BT_Size(root->left);//对左子树进行个数的遍历BT_Size(root->right);//对右子树进行个数的遍历return size;
}

 运行结果:

3.2求二叉树叶节点个数

既然是让咱求叶节点个数,咱就需要知道什么是叶节点:度为0的节点(没有左孩子,也没有右孩子的节点)

通过前面对二叉树的遍历咱们应该渐渐对递归要有些体悟了,当一个问题很大的时候,可以化大为小,化繁为简。这样岂不是很爽!

 分析见下:
 代码:
int BT_Leaf_Size(BT* root)
{if (root == NULL)return 0;if (root->left == NULL && root->right == NULL) // 判断是否为叶节点return 1;return  BT_Leaf_Size(root->left) + BT_Leaf_Size(root->right);
}
3.3求二叉树第H层节点个数

假设根节点所在层次就是第一层,依次下推,第H层的每个节点必然是第(H-1)层节点的左右孩子,这不就解决问题了嘛:求二叉树第H层节点个数转化为求二叉树第H-1层每个节点的左右孩子之和不就OK了。

 分析见下:

 代码:
int BT_LevelH_Size(BT* root, int h)
{if (root == NULL)return 0;if (h == 1)return 1;
return BT_LevelH_Size(root->left, h - 1)+ BT_LevelH_Size(root->right, h - 1) ;
}
4.二叉树高度的求解

对于一个二叉树而言,树的高度是左子树与右子树相比高度最大的一个再加1

还是如此,借助递归思想

子问题:左子树与右子树相比高度最大的一个再加1

结束条件:判断当前节点是否为空,其次当前节点是否为叶节点

 分析见下:

代码:
int BT_Height(BT* root)// 求树高度
{if (root == NULL)return 0;if (root->left == NULL && root->right == NULL)return 1;int left_h = BT_Height(root->left);int right_h = BT_Height(root->right);return left_h > right_h ? left_h + 1 : right_h + 1;
}
5.二叉树的销毁

注意这是链式二叉树,不能直接进行删除,更为直接的是采用后续遍历来进行销毁

代码:

void BT_Destroy(BT* root)
{if (root == NULL)return;BT_Destroy(root->left);BT_Destroy(root->right);free(root);
}
6.二叉树的层次遍历

 对于二叉树的层次遍历很显然就是一层一层进行遍历,在这里借助队的性质先进先出,来实现对二叉树的层次遍历

当队头元素出队的时候同时让队头元素的左右孩子节点也进队

 这里需要借助咱们之前写的队列的相关代码才可以玩哟!

 代码:
void Level_order(BT* root)
{Queque q;QuqueInit(&q);QuquePush(&q, root);while (!QuequeEmpty(&q)){BT* front = QuequeFront(&q);//取队头元素if (front)printf("%d ", front->val);QuquePop(&q);//删除队头元素//队头元素的左右孩子进队if (front)  {QuquePush(&q, front->left);QuquePush(&q, front->right);}}QuqueDestroy(&q);
}
7.借助二叉树前序遍历的结果实现对二叉树的构建

 分析: 还是分治的思想,层层递归,直到遇到空返回,把每一个节点看作一个新的根节点,只要当前节点不为空,就继续先序遍历

首先这是一个IO型的,所以完全需要自己手撕代码,

先把当前字符串的内容进行接收

其次利用递归:先序建立二叉树

子问题:先序建立    结束条件:遇到空,直接返回

最后利用递归写一个中序遍历

 辅助:需要我们每一个数据开辟节点

 定义一个二叉树的结构体:

 递归建立二叉树:

注意这里必须是 (*pi)++,不能是 *pi++。因为是递归调用每一次都需要进行栈帧建立,这样就不能做保证下标正确性,问题本质同二叉树求节点个数中的size

 完整代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>typedef char BT_DataType; // 存储char类型数据
typedef struct BinaryTreeNode
{struct BinaryTreeNode* left;struct BinaryTreeNode* right;BT_DataType val;}BT;
BT* BuyNode( BT_DataType x)
{BT*  n1 = (BT*)malloc(sizeof(BT));if (n1 == NULL)return NULL;n1->val = x;n1->left = n1->right = NULL;return n1;
}
BT* CreateTree(char*pa,int* pi){if(pa[*pi] == '#'){(*pi)++; // err *(pi)++return NULL;}BT*root = BuyNode(pa[*pi]);(*pi)++; // err *(pi)++root->left = CreateTree(pa,pi );root->right =CreateTree(pa,pi );return root;}void  In_Order(BT*root){if(root == NULL)return ;In_Order(root->left);printf("%c ",root->val);In_Order(root->right);}int main() 
{char a[100];scanf("%s",a);int i = 0;// 下标访问数组BT* root =  CreateTree(a,&i);In_Order(root);
}
8.判断一棵树是否为完全二叉树

 对于这个,可以借助层次遍历的思想来玩。只要是在出队的时候连续全部为空即为完全二叉树;否则就不是完全二叉树

 

代码:

bool BT_Complete(BT* root)
{Queque q;QuqueInit(&q);QuquePush(&q, root);while (!QuequeEmpty(&q)){BT* front = QuequeFront(&q);//取队头元素QuquePop(&q);//删除队头元素if (front == NULL)break;QuquePush(&q, front->left);QuquePush(&q, front->right);}//来到这只能有2种情况:队列为空  front == NULLwhile (!QuequeEmpty(&q)){//只能是front为空BT* front1 = QuequeFront(&q);//取队头元素if(front1)return false;  //非空 说明不是二叉树QuquePop(&q);}QuqueDestroy(&q);return true;
}
9:二叉树的查找

查找某个节点的值是否存在,若存在则返回该节点的地址,否则返回NULL

可以用先序来遍历

 错误示例:当我想查找3这个节点时候

 相信有不少老铁们一开始就会这样写吧,但是明明3这个节点存在为什么会没有找到呢?

其实通过我们调试发现这样写的逻辑是有Bug的,及时当要查找的节点存在时,也会造成把已找到的节点覆盖掉,其实这个查找逻辑的代码重在对return 返回的考察

正确代码:
BT* BT_Find(BT* root, BT_DataType x) // 3
{if (root == NULL)return  NULL;if (root->val == x)return root; //先去左子树查找BT* left = BT_Find(root->left, x);if (left)return left;//说明左子树不存在,去右子树查找BT* right = BT_Find(root->right, x);if (right)return right;//来到这说明左右子树都不存在return NULL;
}

 运行结果:

10.二叉树相关OJ的练习
10.1 单值二叉树

 解题分析:

其实一个遍历就直接搞定了。拿双亲节点的值与孩子节点对应的值进行比较,若是不相等直接 return false

是不是看着比较简单,但是写起来是有坑滴

 

 运行结果:

其实走读一遍代码大概就找到问题所在了:return 语句返回是返回到调用当前函数的上一个函数里,并不是直接返回到最外层 

这个问题的本质同二叉树查找指定数据是一样的

OJ代码:
bool isUnivalTree(struct TreeNode* root) 
{if(root == NULL)return true;if(root->left){if(root->val != root->left->val)return false;}if(root->right){if(root->val != root->right->val)return false;}//递归左子树bool ret1 = isUnivalTree(root->left);//递归右子树bool ret2=  isUnivalTree(root->right);return ret1 && ret2;
}
10.2 判断2个二叉树是否相同

 解题分析:

采用遍历的方式,根节点与根节点进行对比,左孩子与左孩子对比,右孩子与右孩子对比,注意是对比val而不是对比节点所对应的地址

OJ代码:
bool isSameTree(struct TreeNode* p, struct TreeNode* q){if(p == NULL &&q == NULL)return true;if(p == NULL || q == NULL)return false;//来到这说明都不为空if(p->val  != q->val)return false;return  isSameTree(p->left,q->left) && isSameTree(p->right,q->right);//注意这里必须是 逻辑且的关系,不能是逻辑或
}

 OK,以上就是我今日要为大家分享的,希望各位都有得!对于二叉树这部分是数据结构初阶比较难的一部分了,初学的时候是不好理解,事事都有个过渡期,当然自己有空的时候也不要忘了敲敲代码!

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

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

相关文章

【MicroPython】SPI层次结构SPI协议与SPI控制器结构

文章目录 前言一、SPI 程序层次1.1 硬件原理图1.2 硬件框图1.3 软件层次 二、SPI协议2.1 硬件连线2.2 如何访问SPI设备2.3 SPI 框图 总结 前言 SPI&#xff08;Serial Peripheral Interface&#xff09;是一种常见的串行通信协议&#xff0c;用于在微控制器和外部设备之间进行…

Redis 【1】—— 安装 与 配置

Redis 【1】—— 安装 与 配置 一、安装 与 配置&#xff08;一&#xff09;使用 yum 安装&#xff08;二&#xff09;创建符号链接1. 软链接2. 相关指令 &#xff08;三&#xff09;修改配置文件&#xff08;四&#xff09;Redis 的启停 一、安装 与 配置 &#xff08;一&…

华为配置WLAN高密业务示例

配置WLAN高密业务示例 组网图形 图1 配置高密WLAN环境网络部署组网图 业务需求组网需求数据规划配置思路配置注意事项操作步骤配置文件 业务需求 体育场由于需要接入用户数量很大&#xff0c;AP间部署距离较小&#xff0c;因此AP间的干扰较大&#xff0c;可能导致用户上网网…

小甲鱼Python07 函数初级

一、创建和调用函数 pass语句表示一个空的代码块&#xff0c;我们经常先写好函数&#xff0c;pass占一个坑&#xff0c;等规划好之后再来填坑。 函数也是可以指定参数的&#xff0c;我们会把参数传进去用来替代形参。 在Python里如果想要返回值&#xff0c;不需要指定函数的返…

人工智能图像编辑工具遭网络攻击,2000 万用户数据信息泄露

人工智能图像编辑工具 Cutout.Pro 近期发生一起严重数据泄露事件&#xff0c;约 2000 万会员用户的电子邮件地址、散列和加盐密码、IP 地址以及姓名等敏感信息被放在数据泄露论坛上出售。 Cutout.Pro 是一个人工智能驱动的照片和视频编辑平台&#xff0c;可用于图像增强、背景移…

33. 【Linux教程】Linux 用户组

前面小节介绍了 Linux 用户相关的增删改查&#xff0c;本小节介绍 Linux 用户组&#xff0c;Linux 系统中采取了一种安全机制&#xff08;即用户组&#xff09;&#xff0c;用户组可以允许多个 Linux 用户共享同一种权限。 1. 用户组介绍 Linux 是多任务多用户的操作系统&…

为什么推荐大家学习双拼输入法?

在现代信息技术迅速发展的今天&#xff0c;计算机已经成为人们日常工作生活中不可分割的一部分。而对于大多数人来说&#xff0c;输入法是与计算机打交道最频繁的交互方式之一。在中文输入法中&#xff0c;五笔输入法一直是非常受欢迎的一种输入方式。然而&#xff0c;双拼输入…

C++用临时对象构造新对象

C用临时对象构造新对象 //用临时对象构造同类型的新对象&#xff0c;该临时对象不产生&#xff1b; // 直接用生成临时对象的方法构造新对象&#xff0c;这是编译器对代码的优化&#xff0c;效率更高 #include<iostream> using namespace std; class MyClass { public:…

springboot 实现本地文件存储

springboot 实现本地文件存储 1、存储介绍 服务端接收上传的目的是提供文件的访问服务&#xff0c;对于SpringBoot而言&#xff0c;其对静态资源访问提供了很好的支持&#xff0c;使用其提供的基本默认配置可以满足开发需求&#xff0c;同时&#xff0c;又支持开发人员进行自定…

【Sql server】假设有三个字段a,b,c 以a和b分组,如何查询a和b唯一,但是c不同的记录

欢迎来到《小5讲堂》&#xff0c;大家好&#xff0c;我是全栈小5。 这是《Sql Server》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对…

React富文本编辑器开发(五)

到目前为止我们所有的功能操作都是直接写在 onKeydown 事件里了&#xff0c;但如果我想复用相同的功能怎么办呢&#xff0c;最好的办法就是拨离了&#xff0c;下面我就形如进行这样的操作&#xff0c;把相关的可复用的命令操作抽取出来。 新建文件 _helper.jsx,创建一个协助器…

动态规划(算法竞赛、蓝桥杯)--单调队列滑动窗口与连续子序列的最大和

1、B站视频链接&#xff1a;E11【模板】单调队列 滑动窗口最值_哔哩哔哩_bilibili 题目链接&#xff1a;滑动窗口 /【模板】单调队列 - 洛谷 #include <bits/stdc.h> using namespace std; const int N1000010; int a[N],q[N];//q存的是元素的下标 int main(){int n,k;…