二叉树链式结构的实现(二叉树的遍历以及各种常用功能函数的实现)

之前也是把堆部分的知识点梳理完毕(即二叉树链式顺序的实现):堆的应用:堆排序和TOP-K问题

那么讲完了二叉树链式结构的实现。今天就进入二叉树链式结构的实现:


文章目录

  • 1.准备工作
  • 2.二叉树的遍历
    • 2.1前序遍历
    • 2.2中序遍历
    • 2.3后序遍历
    • 2.4层序遍历
  • 3.节点个数以及高度等
    • 3.1二叉树节点个数
    • 3.2二叉树叶子节点(度为1的节点)个数
    • 3.3树的高度
    • 3.4二叉树第k层节点个数
    • 3.5二叉树查找值为x的节点
  • 4.二叉树的构建及销毁
    • 4.1通过前序遍历的数组构建二叉树
  • 4.2二叉树的销毁
  • 5.判断二叉树是否是完全二叉树


1.准备工作

我们先手动快速创建一棵简单的二叉树来先进入二叉树操作,等对二叉树递归和结构有了一定的熟练后我们反过头再来看二叉树真正的创建方式

这不是真正的创建方法,是自己手动构建

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<math.h>typedef int BTDataType;
typedef struct BinaryTreeNode//节点
{BTDataType data;//数据struct BinaryTreeNode* left;//左孩子struct BinaryTreeNode* right;//右孩子
}TreeNode;TreeNode* BuyTreeNode(int x)//创建数据为x的节点
{TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));assert(node);node->data = x;node->left = NULL;node->right = NULL;return node;
}TreeNode* CreateTree()//形成树(手动连接)
{TreeNode* node1 = BuyTreeNode(1);TreeNode* node2 = BuyTreeNode(2);TreeNode* node3 = BuyTreeNode(3);TreeNode* node4 = BuyTreeNode(4);TreeNode* node5 = BuyTreeNode(5);TreeNode* node6 = BuyTreeNode(6);//创建六个节点node1->left = node2;node1->right = node4;node2->left = node3;node4->left = node5;node4->right = node6;//进行连接return node1;
}

构建的二叉树如图:

请添加图片描述

2.二叉树的遍历

递归实现的关键在于将问题分解为规模更小的子问题,然后通过函数的递归调用来解决子问题。在二叉树遍历中,递归的思想可以很自然地应用,因为遍历左子树和右子树本质上就是对规模更小的子树进行遍历

二叉树遍历(Traversal)是指按照一定规则,依次访问二叉树中的所有节点,每个节点只被访问一次

二叉树的遍历有:前序/中序/后序的递归结构遍历和层序遍历

  1. 前序遍历(Preorder Traversal):先访问根节点,再访问左子树,最后访问右子树(根>左>右)
  2. 中序遍历(Inorder Traversal):先访问左子树,再访问根节点,最后访问右子树(左>根>右)
  3. 后序遍历(Postorder Traversal):先访问左子树,再访问右子树,最后访问根节点(左>右>根)
  4. 层序遍历是一种按层级顺序逐层遍历树结构的方法。它从树的根节点开始,逐层向下遍历,按照从左到右的顺序访问每一层的节点

2.1前序遍历

先访问根节点,再访问左子树,最后访问右子树(根>左>右);每进入一个新节点(先访问自身)

按照此规则之前构建的二叉树访问结果:1 2 3 NULL NULL NULL 4 5 NULL NULL 6 NULL NULL

方便大家理解,我就把NULL也写上:

请添加图片描述

void PreTraversal(TreeNode* root) 
{if (root == NULL) {printf("NULL ");//正常是不需要的,这里是为了让大家看到NULL(看的全过程)return;}// 访问根节点printf("%d ", root->data);// 遍历左子树PreTraversal(root->left);// 遍历右子树PreTraversal(root->right);
}int main()
{TreeNode* root = CreateTree();//创建好树PreTraversal(root);return 0;
}

结果:
请添加图片描述

2.2中序遍历

先访问左子树,再访问根节点,最后访问右子树(左>根>右);每进入一个新节点(先访问左子树)

按照此规则之前构建的二叉树访问结果:NULL 3 NULL 2 NULL 1 NULL 5 NULL 4 NULL 6 NULL

请添加图片描述

void InTraversal(TreeNode* root)
{if (root == NULL){printf("NULL ");//正常是不需要的,这里是为了让大家看到NULL(看的全过程)return;}// 遍历左子树InTraversal(root->left);// 访问根节点printf("%d ", root->data);// 遍历右子树InTraversal(root->right);
}int main()
{TreeNode* root = CreateTree();//创建好树InTraversal(root);return 0;
}

结果:

请添加图片描述

2.3后序遍历

先访问左子树,再访问右子树,最后访问根节点(左>右>根);每进入一个新节点(先访问右子树)

按照此规则之前构建的二叉树访问结果:NULL NULL 3 NULL 2 NULL NULL 5 NULL NULL6 4 1

请添加图片描述

void PostTraversal(TreeNode* root)
{if (root == NULL){printf("NULL ");//正常是不需要的,这里是为了让大家看到NULL(看的全过程)return;}// 遍历左子树PostTraversal(root->left);// 遍历右子树PostTraversal(root->right);// 访问根节点printf("%d ", root->data);
}int main()
{TreeNode* root = CreateTree();//创建好树PostTraversal(root);return 0;
}

结果:

请添加图片描述

2.4层序遍历

它从树的根节点开始,逐层向下遍历,按照从左到右的顺序访问每一层的节点

在 C 语言中,一般使用队列来实现层序遍历:

  1. 从根节点开始,将根节点入队列

  2. 循环执行以下步骤,直到队列为空:

    • 弹出队列的首节点,并访问该节点
    • 将该节点的左右子节点(如果存在)依次入队列;即弹出节点后把子节点入队列

    队列为空时,循环结束

void LevelOrder(TreeNode* root)
{Queue q;//创建队列,队列节点里的data储存root的地址QInit(&q);//初始化QPush(&q, root);//根进去了int LevelSize = 1;//要一层一层输出,来辅助while (!QEmpty(&q)){while (LevelSize--)//LevelSize是几就循环几次,一开始是1,输出一个就换行了,再Push后再次更新,来输出下一行{TreeNode* first = QFront(&q);//先存下来QPop(&q);//再pop掉第一个printf("%c ", first->data);if (first->left)//有左节点就push进来{QPush(&q, first->left);}if (first->right)//有右节点就push进来{QPush(&q, first->right);}}printf("\n");LevelSize = QSize(&q);}QDestroy(&q);
}

请添加图片描述


3.节点个数以及高度等

3.1二叉树节点个数

同样是递归

  1. 计算二叉树节点个数的问题就是计算左子树节点个数和计算右子树节点个数两个子问题
  2. 对于当前节点,我们需要计算以当前节点为根的子树的节点个数。这个节点本身占据一个节点(后面加一),所以我们将问题拆分为计算左子树节点个数和计算右子树节点个数两个子问题,然后将左右子树节点个数相加,并加上当前根节点,即 TreeSize(root->left) + TreeSize(root->right) + 1
  3. 对于左子树和右子树,我们同样可以按照上述步骤继续拆分分析,直到遇到空节点为止(递归终止条件)
int TreeSize(TreeNode* root)
{if (root == NULL){return 0;}return TreeSize(root->left) + TreeSize(root->right) + 1;
}int main()
{TreeNode* root = CreateTree();//创建好树printf("%d\n", TreeSize(root));return 0;
}

结果:

请添加图片描述

代码也可以这样写:

int TreeSize(TreeNode* root)
{return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;//利用三目运算符
}

3.2二叉树叶子节点(度为1的节点)个数

  1. 判断当前节点是否为空。如果为空,子树的叶子节点个数为 0,直接返回 0
  2. 节点不为空,我们需要判断当前节点是否为叶子节点。如果当前节点的左右子树均为空,即 root->left == NULL && root->right == NULL,则表示当前节点为叶子节点,返回 1
  3. 不是叶子节点,则需要计算以当前节点为根的子树的叶子节点个数。这个节点本身不是叶子节点,所以我们将问题拆分为计算左子树叶子节点个数和计算右子树叶子节点个数两个子问题,然后将左右子树叶子节点个数相加,即 TreeLeafSize(root->left) + TreeLeafSize(root->right)
  4. 对于左子树和右子树,我们同样可以按照上述步骤继续拆分,直到遇到空节点或叶子节点为止
int TreeLeafSize(TreeNode* root)
{if (root == NULL)//是NULL就返回0{return 0;}if (root->left == NULL && root->right == NULL)//是叶子就返回1{return 1;}return TreeLeafSize(root->left) + TreeLeafSize(root->right);//不是叶子就返回左子树叶子与右子树叶子之和
}int main()
{TreeNode* root = CreateTree();//创建好树//printf("%d\n", TreeSize(root));printf("%d\n", TreeLeafSize(root));return 0;
}

请添加图片描述

3.3树的高度

  1. 如果树为空,返回高度为 0
  2. 如果树不为空,则树的高度等于左子树的高度和右子树的高度中的较大值加 1,即 fmax(TreeHeight(root->left), TreeHeight(root->right)) + 1
  3. 对于左子树和右子树,我们同样可以按照上述步骤继续递归计算
int TreeHeight(TreeNode* root)
{if (root == NULL)return 0;return fmax(TreeHeight(root->left), TreeHeight(root->right)) + 1;
}int main()
{TreeNode* root = CreateTree();//创建好树//printf("%d\n", TreeSize(root));//printf("%d\n", TreeLeafSize(root));printf("%d\n", TreeHeight(root));return 0;
}

请添加图片描述

3.4二叉树第k层节点个数

  1. 如果树为空,返回节点数为 0
  2. 如果 k 等于 1,表示计算树的根节点,返回 1
  3. 如果 k 大于 1,则第 k 层节点数等于左子树的第 k-1 层节点数加上右子树的第 k-1 层节点数,即 TreeLevelNodes(root->left, k-1) + TreeLevelNodes(root->right, k-1)
  4. 对于左子树和右子树,我们同样可以按照上述步骤继续递归计算
int TreeLevelKSize(TreeNode* root, int k)
{if (root == NULL)return 0;if (k == 1)return 1;return TreeLevelKSize(root->left, k - 1) + TreeLevelKSize(root->right, k - 1);
}int main()
{TreeNode* root = CreateTree();//创建好树printf("%d\n", TreeLevelKSize(root,2));//求个第二层的return 0;
}

3.5二叉树查找值为x的节点

  1. 如果为空,那肯定找不到,直接返回NULL(同时也作为递归终止的情况之一)
  2. 先找跟节点,找到了即root->data == x,就返回该节点地址(同时也作为递归终止的情况之一)
  3. 找不到,就递归左节点,再右节点
  4. 都没有return的机会后,最后return NULL

递归停止的两种情况:要么找到了返回地址, 要么没找到返回NULL

  • 当不是NULL时,说明找到了,就一路返回上去,得到结果
  • 是NULL,就说明没找到,就去另外一边继续
TreeNode* TreeFind(TreeNode* root, BTDataType x)
{if (root == NULL){return NULL;}// 先看根节点if (root->data == x){return root;}// 看左子树TreeNode* ret1= TreeFind(root->left, x);//递归停止的两种情况:要么找到了返回地址// 要么没找到返回NULL//当不是NULL时,说明找到了,就一路返回上去,得到结果if (ret1 != NULL){return ret1;}// 看右子树TreeNode* ret2 = TreeFind(root->right, x);//如上if (ret2 != NULL){return ret2;}return NULL;
}int main()
{TreeNode* root = CreateTree();//创建好树//printf("%d\n", TreeSize(root));//printf("%d\n", TreeLeafSize(root));//printf("%d\n", TreeHeight(root));//printf("%d\n", TreeLevelKSize(root,2));//求个第二层的printf("%p\n", TreeFind(root, 5));return 0;
}

请添加图片描述

返回地址,说明找到了


4.二叉树的构建及销毁

4.1通过前序遍历的数组构建二叉树

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

先根据这个画出来(#为NULL就不画了):

请添加图片描述

TreeNode* CreateTreeTrue(char* arr, int* pi)
{if (arr[*pi] == '#'){(*pi)++;//调到下一个字符return NULL;}TreeNode* root = (TreeNode*)malloc(sizeof(TreeNode));assert(root);root->data = arr[*pi];//根创建且赋值好了(*pi)++;root->left=CreateTreeTrue(arr, pi);//创建左树root->right=CreateTreeTrue(arr, pi);//创建右树return root;
}

严格按照> 左子树> 右子树的顺序,每一个左子树又是作为一个根,有自己的左子树及右子树。所以以创建根的方法来创建左子树和右子树,直到遇到#来返回NULL进行终止

4.2二叉树的销毁

销毁我们都使用后序销毁

void TreeDestory(TreeNode** root)
{if (*root == NULL){return;}TreeDestory((*root)->left);TreeDestory((*root)->right);free(*root);*root = NULL;
}

因为最后要把root置空,传一级指针不行(没用,形参是临时拷贝),想要改变root本身就传入root的地址(即二级指针


5.判断二叉树是否是完全二叉树

也是利用队列,pop出一个后把他的左右节点push进去,遇到NULL就出循环,再看此时队列里是否全是NULL,如果全是NULL那就是完全二叉树,只要还有节点,那就不是完全二叉树

int BinaryTreeComplete(TreeNode* root)
{Queue q;QInit(&q);QPush(&q, root);//根进去了int LevelSize = 1;while (!QEmpty(&q)){TreeNode* first = QFront(&q);QPop(&q);if (first == NULL)break;//通过这个出循环QPush(&q, first->left);QPush(&q, first->right);}// 已经遇到空节点,如果此时队列中后面的节点还有非空,就不是完全二叉树while (!QEmpty(&q)){TreeNode* first = QFront(&q);QPop(&q);if (first != NULL){QDestroy(&q);return 0;}}QDestroy(&q);return 1;
}

终于啊,二叉树中较为基本的地方都已经梳理完毕了,相信在不久的将来,还会有二叉树的内容,大家敬请期待吧!!!

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

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

相关文章

【C语言】程序练习(四)

大家好&#xff0c;这里是争做图书馆扫地僧的小白。非常感谢各位的支持&#xff0c;也期待着您的关注。 目前博主有着C语言、C、linux以及数据结构的专栏&#xff0c;内容正在逐步的更新。 希望对各位朋友有所帮助同时也期望可以得到各位的支持&#xff0c;有任何问题欢迎私信与…

数学公式编译器MathType下载与安装

下载网址&#xff1a;下载 MathType - WIRIS Store 1.点击【下载MathType for Windows】 2、点击中文版 3.找到所下载的目录&#xff1a; 右击-->以管理员身份运行 4、新建word文档 点击文件->账户->关于word 5.点击【文件】、【选项】&#xff0c;❶点击【加载项】…

Elasticsearch:结合 ELSER 和 BM25 文本查询的相关搜索

Elastic Learned Spare EncodeR (ELSER) 允许你执行语义搜索以获得更相关的搜索结果。 然而&#xff0c;有时&#xff0c;将语义搜索结果与常规关键字搜索结果相结合以获得最佳结果会更有用。 问题是&#xff0c;如何结合文本和语义搜索结果&#xff1f; 首先&#xff0c;让我…

SAP BAPI 客户主数据创建:cmd_ei_api=>maintain_bapi

BAPI函数&#xff1a;cmd_ei_api>maintain_bapi 事物代码&#xff1a;XD01/XD02 客户主数据创建、修改、拓展功能开发 数据结构定义&#xff1a; 基本视图信息 公司代码信息结构&#xff1a; 销售视图信息结构: 客户主数据税分类信息结构&#xff1a; 代码参考 详细代码…

众和策略:高股息板块持续红火,煤炭股会否走上慢牛重估之路?

1月3日&#xff0c;煤炭板块连续此前活跃走势&#xff0c;走出日线三连阳。截至收盘&#xff0c;东方财富煤炭板块全体上涨1.75%。盘面显示&#xff0c;35只煤炭股仅有3只股价跌落&#xff0c;其中&#xff0c;云煤动力&#xff08;600792.SH&#xff09;涨停&#xff0c;郑州煤…

Linear Regression 线性回归

深度学习&#xff1a; 数据集模型选择训练&#xff08;KNN不需要&#xff09;推理&#xff08;预测&#xff09; 假设学生用x小时学习深度学习&#xff0c;能够得要y分数&#xff1a; 那么学习4小时&#xff0c;能够得到多少分&#xff1f; 用已知数据作为训练集&#xff1a…

MySQL8.0主从复制实现及遇到的个人问题

文章目录 1、准备两个服务器或者虚拟机2、主库配置3、从库配置4、配置过程中使用到的命令5、遇到的问题 1、准备两个服务器或者虚拟机 这里使用的VM虚拟机的Centos、MySQL版本是8.0.26、使用FinalShell进行远程操作。 2、主库配置 修改MySQL配置文件(/etc/my.cnf) #启用二进…

ROS学习记录:在ROS中用C++实现激光雷达避障

前言 本文建立在成功获取激光雷达数据的基础上&#xff0c;详细参考 在ROS中用C实现获取激光雷达的数据 一、实现思路 二、在VScode中打开之前编写好的lidar_node.cpp 三、在lidar_node.cpp中写入如下代码 #include <ros/ros.h> #include <std_msgs/String.h> …

thinkadmin安装步骤

一,先cmd运行安装命令 ### 创建项目&#xff08; 需要在英文目录下面执行 &#xff09; composer create-project zoujingli/thinkadmin二,在confing中的database.php配置数据库 三,将仓库的data复制到app目录下 https://gitee.com/zoujingli/think-plugs-data 四,在cmd运…

MySQL 8.0 开关 Redo Logging

一 前言 前几天有客户测试使用云数据库的时候提出 要禁止mydumper 关闭redo log的操作 (说白了就是导入数据时保持MySQL 实例的redo logging功能)&#xff0c; 这才想起 在 MySQL 8.0.21 版本中&#xff0c;开启了一个新特性 “Redo Logging 动态开关”。 在新实例导数据的场…

Sectigo泛域名https证书有什么用

Sectigo旗下有泛域名https证书实现了同时为多个域名网站提供安全加密服务&#xff0c;虽然将域名网站的类型限制在了域名以及域名旗下的二级子域名中。Sectigo旗下的泛域名https证书分为DV基础型和OV企业型&#xff0c;提高了https证书对各个场景的适配。今天就随SSL盾小编了解…

迅为RK3588开发板使用 FFMpeg 进行推流

Debian/Ubuntu 系统使用以下命令安装 FFMpeg &#xff0c;如下图所示&#xff1a; apt-get install ffmpeg 使用 ifconfig 查看开发板 ip 为 192.168.1.245 如下图所示&#xff1a; 使用 FFMpeg 推流一个 mp4 视频进行测试&#xff0c;作者将测试视频 test.mp4 放在了根目录下…