用C语言把一棵普通二叉树安排得明明白白

1. 树的相关术语

结点的度:一个结点含有的子树的个数称为该结点的度; 如上图:A的为6

叶结点或终端结点:度为0的结点称为叶结点; 如上图:B、C、H、I...等结点为叶结点

非终端结点或分支结点:度不为0的结点; 如上图:D、E、F、G...等结点为分支结点

双亲结点或父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点; 如上图:A是B的父结点

孩子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点; 如上图:B是A的孩子结点

兄弟结点:具有相同父结点的结点互称为兄弟结点; 如上图:B、C是兄弟结点

树的度:一棵树中,最大的结点的度称为树的度; 如上图:树的度为6

结点的层次:从根开始定义起,根为第1层,根的子结点为第2层,以此类推;

树的高度或深度:树中结点的最大层次; 如上图:树的高度为4

堂兄弟结点:双亲在同一层的结点互为堂兄弟;如上图:H、I互为兄弟结点

结点的祖先:从根到该结点所经分支上的所有结点;如上图:A是所有结点的祖先

子孙:以某结点为根的子树中任一结点都称为该结点的子孙。如上图:所有结点都是A的子孙

森林:由m(m>0)棵互不相交的树的集合称为森林;

满二叉树:深度为k且有2^{k}-1个结点的二叉树。在满二叉树中,每层结点都是满的,即每层结点都具有最大结点数。

满二叉树的顺序表示:从二叉树的根开始,按层间从上到下,层内从左到右的顺序进行编号(0,1,2,……,n-1)。

完全二叉树:深度为k,结点数为n的二叉树,如果其结点0~n-1的位置序号分别与等高的满二叉树的结点1~n-1的位置序号一一对应,则为完全二叉树。完全二叉树的特点为:(1)叶子结点只可能出现在最后两层;(2)度数为1的结点个数为0或1。

 2. 二叉树的性质

性质1:在二叉树的第i层上至多有2^{i-1}个结点(i>=1)

性质2:深度为k的二叉树至多有2^{k}-1个结点(k>=1)

性质3:对任意一棵二叉树T,若终端结点数为n_{1},而其度数为2的结点数为n_{2},则                             n_{1} = n_{2}+1

性质4:具有n个结点的完全二叉树的深度为(log_{2}n)+1

性质5:对于具有n个结点的完全二叉树,如果按照从上到下,从左到右的顺序对完全二叉树               中的所有结点从0开始编号,则对于任意序号为i的结点,其双亲结点的序号为                         (i-1)/2,左孩子的序号为2*i+1,右孩子的序号为2*i+2。

证明略,但是上面的性质5在堆中有较为重要价值,在上一篇文章中有证明:全面详解堆-CSDN博客 

1. 二叉树的声明以及要实现的接口

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);
//树的高度
int TreeHeight(BTNode* root);

2. 通过前序遍历数组构建二叉树

在对二叉树进行操作之前,我们需要有一棵二叉树。

根据二叉树的用途不同,在二叉树中插入元素的方式繁多且复杂,在初学阶段不需要太多的了解。

于是,我们可以采用暴力构建二叉树的方式,也就是手动实现结点之间的相互链接关系:

BTNode* BuyNode(BTDataType x)
{BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));if (newnode == NULL){perror("malloc fail");exit(-1);}newnode->data = x;newnode->left = NULL;newnode->right = NULL;return newnode;
}BTNode* CreatBinaryTree()
{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;
}

但是,按照这种方式来构建树,每个不同的树都需要我们编写逻辑来实现,十分的麻烦。

由于包含空指针表示的前序遍历序列与二叉树之间存在着一一对应的关系,所以,我们可以采用将前序遍历数组还原为树的方式来构建树(前序最好理解,中序后序调整子树创建顺序即可):

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

a为存放前序遍历序列的数组,'#'表示当前结点为空指针;

n表示数组a的元素个数;

pi为表示当前数组下标的变量的指针,初始值为0,每有一个元素加入到树中,(*pi)++。

3. 二叉树销毁

 这里采用的是后序遍历来释放结点,否则需要定义变量来记录左右子树的根结点。

// 二叉树销毁
void BinaryTreeDestory(BTNode** root)
{if (*root == NULL)return;else{BinaryTreeDestory(&((*root)->left));BinaryTreeDestory(&((*root)->right));free(*root);*root = NULL;}
}

4. 二叉树的结点个数

利用递归即可解决,整棵树的结点数 = 左子树的结点数 + 根结点(1个)+ 右子树的结点数

同理,左右子树也是相同的逻辑。

某棵子树(某结点的左子树或右子树)为空时,返回0。

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

5. 二叉树的叶子结点数

与上面的算法相似,但是此处我们只统计叶子结点数。

叶子结点区别于其他结点的特点是,其左右孩子都为空,如果当前结点符合条件我们就返回1,表示当前结点为叶子结点。

假如当前结点不是叶子结点,那么:

以当前结点为根的子树的叶子结点数 = 左子树叶子结点数 + 右子树叶子结点数。

等式中不再包含根结点。

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

6. 二叉树第k层的结点数

同样的递归思路,第k层结点数 = 左子树第k-1层结点数 + 右子树第k-1层结点数

但是我们需要一个参数k来判断当前结点位于第几层,以及是否需要返回。

每深入一层,k就减1,当k等于1时返回1,表示已找到一个所求结点。

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

7. 查找值为x的结点

如果当前结点的值为x,则直接返回当前结点。

否则,先到左子树进行寻找,若左子树没有(返回值为NULL)就找右子树。

// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{if (root == NULL)return NULL;if (root->data == x)return root;BTNode* Find = BinaryTreeFind(root->left, x);if (Find == NULL){Find = BinaryTreeFind(root->right, x);}return Find;
}

8.二叉树的递归与非递归遍历

二叉树的递归遍历较为简单,但是非递归算法却十分复杂,需要用到栈来模拟递归进行实现。

在该文章中有详细介绍:栈与递归的实现-CSDN博客

9. 树的高度

树的高度指树中结点的最大层次。

所以树的高度 = 左子树与右子树中较高者的高度 + 1(根结点)。

//树的高度
int TreeHeight(BTNode* root)
{if (root == NULL)return 0;int leftHeight = TreeHeight(root->left);int rightHeight = TreeHeight(root->right);return (leftHeight > rightHeight ? leftHeight : rightHeight) + 1;
}

 10. 层序遍历

层序遍历也就是广度优先遍历,实现这个算法需要用到队列的帮助。

由于C语言不支持队列,所以在这里写了一个简易的队列。

其中,Get函数会在获取队头结点的同时将其出队,这样实现对于我们当前算法的实现来说,使用起来更加方便。

当然,下面这段代码只是帮助理解,直接忽略也可,只要你知道我对队列进行了什么操作。

typedef BTNode* QDataType;// 链式结构:表示队列 
typedef struct QListNode
{struct QListNode* _next;QDataType _data;
}QNode;// 队列的结构 
typedef struct Queue
{QNode* _front;QNode* _rear;int size;
}Queue;//初始化
void Inite(Queue* q)
{q->size = 0;q->_front = NULL;q->_rear = NULL;
}//入队
void Push(Queue* q, QDataType x)
{QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc fail");exit(-1);}newnode->_data = x;newnode->_next = NULL;if (q->_rear == NULL){q->_front = q->_rear = newnode;}else{q->_rear->_next = newnode;q->_rear = newnode;}q->size++;
}//出队
QDataType Get(Queue* q)
{if (q->size == 0)return NULL;QDataType temp = q->_front->_data;QNode* del = q->_front;if (q->size == 1){q->_rear = NULL;}q->_front = q->_front->_next;free(del);q->size--;return temp;
}//销毁队列
void DestroyQueue(Queue* q)
{QDataType temp;while (q->_front){temp = Get(q);}
}

基本思路就是:

1. 出队得到一个结点并访问。

2. 如果当前结点有孩子,则将其孩子全部入队。

3. 不断重复以上两步,知道队中无结点。

// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{Queue q;Inite(&q);Push(&q, root);BTNode* cur = NULL;while (cur = Get(&q)){printf("%c ", cur->data);if(cur->left != NULL)Push(&q, cur->left);if(cur->right != NULL)Push(&q, cur->right);}DestroyQueue(&q);
}

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

根据完全二叉树的特点,我们总结出以下判断标准:

1. 如果某个结点只有右孩子而没有左孩子,则该树一定不是完全二叉树。

2. 如果某个结点不是满的(缺少左孩子或右孩子),则位置序号在其之后的结点一定既没有      左孩子,又没有右孩子,否则就是非完全二叉树。

// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root)
{Queue q;Inite(&q);Push(&q, root);BTNode* cur = NULL;do{cur = Get(&q);if (cur->left == NULL && cur->right != NULL)return 0;else if(cur->left != NULL && cur->right != NULL){Push(&q, cur->left);Push(&q, cur->right);}} while (cur->right);while (q.size != 0){cur = Get(&q);if (cur->left != NULL || cur->right != NULL)return 0;}return 1;DestroyQueue(&q);
}

12. oj练习

12.1 单值二叉树. - 力扣(LeetCode)

第一种思路:保存根结点的值,然后用其与所有结点进行比较。

bool judge(struct TreeNode* root, int com)
{if(root == NULL)return true;if(root->val != com)return false;return judge(root->left, com) && judge(root->right, com);
}bool isUnivalTree(struct TreeNode* root) {int com = root->val;return judge(root, com);
}

为了提高代算法的效率,也可以将com变量改为全局变量。

第二种思路:我们找到题目要求的一个等价条件——除了叶子结点,所有结点的值都与自己孩子的值相等。

bool isUnivalTree(struct TreeNode* root) {if(root == NULL)return true;if(root->left && root->left->val != root->val || root->right && root->right->val != root->val)return false;return isUnivalTree(root->left) && isUnivalTree(root->right);
}

12.2 相同的树. - 力扣(LeetCode)

采用递归的思路,两棵树相同意味着他们:根结点相同,左子树相同,右子树相同。

左右子树相同,包括左右子树都为空的情况。

bool isSameTree(struct TreeNode* p, struct TreeNode* q) {if(p == NULL && q == NULL)return true;if(p == NULL || q == NULL || p->val != q->val)return false;return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}

12.3 对称的树. - 力扣(LeetCode)

 

观察示例可以发现,这道题可以采用与上道题如出一辙的思路,只不过我们要比较的两棵树是根结点的左右子树。

同时,比较的逻辑也需要更改,左子树的右孩子应该与右子树的左孩子比较,左子树的左孩子应该与右孩子的右子树比较。

bool judge(struct TreeNode* root1, struct TreeNode* root2)
{if(root1 == NULL && root2 == NULL)return true;if(root1 == NULL || root2 == NULL || root1->val != root2->val)return false;return judge(root1->left, root2->right) && judge(root1->right, root2->left);
}bool isSymmetric(struct TreeNode* root) {return judge(root->left, root->right);
}

12.4 另一棵树的子树. - 力扣(LeetCode)

这道题可以利用到“相同的树”那道题的函数,我们只需要遍历root,然后将其的子树依次与subRoot进行比较即可。

bool isSameTree(struct TreeNode* p, struct TreeNode* q) {if(p == NULL && q == NULL)return true;if(p == NULL || q == NULL || p->val != q->val)return false;return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot){if(root == NULL)return false;if(isSameTree(root, subRoot))return true;return isSubtree(root->left, subRoot) || isSubtree(root->right, subRoot);
}

12.5 二叉树的前序遍历. - 力扣(LeetCode)

 这道题并不是简单地对二叉树进行前序遍历,而是要求我们返回存储前序遍历序列的数组。

首先需要动态开辟一个存储结果的数组,但问题是开辟多少合适呢?

当然,在这道题中我们可以直接开辟100个空间的数组。

假如我们不知道这么一个条件呢?

可以用动态的顺序表来管理,但鉴于需要我们自己实现,似乎就有点麻烦。

这时,我们就可以用到上面的BinaryTreeSize函数了,先求出树中的结点个数,再进行我们接下来的操作。

与利用前序遍历数组构建二叉树相同,我们这里也需要一个pi来指示数组当前的下标。

下面的这段代码中,直接用returnSize来充当了这个pi。

int TreeSize(struct TreeNode* root)
{return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}void pretraversal(struct TreeNode* root, int* arr, int* pi)
{if(root == NULL)return;arr[*pi] = root->val;(*pi)++;pretraversal(root->left, arr, pi);pretraversal(root->right, arr, pi);
}int* preorderTraversal(struct TreeNode* root, int* returnSize) {int* arr = (int*)malloc(sizeof(arr) * TreeSize(root));*returnSize = 0;pretraversal(root, arr, returnSize);return arr;
}

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

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

相关文章

【Linux】-Tomcat安装部署[12]

目录 简介 安装 安装部署JDK环境 解压并安装Tomcat 简介 Tomcat是由Apache开发的一个Servlet容器,实现了对Servlet和JSP的支持,并提供了作为Web服务器的一些特有功能,如Tomcat管理和控制平台、安全域管理和Tomcat阀等。 简单来说&#…

【软件工程】【23.10】p2

关键字: 软件复用技术、过程途径、特定需求是文档核心、数据字典条目、高内聚低耦合独立性、数据流图映射模块结构图、UML依赖、用例图关系、RUB迭代、程序规格说明等价类划分、有效性测试的目标、喷泉模型面向对象、软件验证过程、CMMI

【Linux】Centos7安装RabbitMQ

【Linux】Centos7安装RabbitMQ 下载 从 rabbitmq 的 GitHub 仓库下载 https://github.com/rabbitmq/rabbitmq-server/releases rabbitmq 是 erlang 语言编写的,需要先安装 erlang https://github.com/rabbitmq/erlang-rpm/releases 安装 使用rz命令上传 erlang 和 …

Polar 网站被黑

Polar 网站被黑 开题,挺好看的前端,可惜啥也没有。 信息搜集一波,扫目录出现几个敏感目录,但是没什么用。 继续搜集,在返回包中发现了HINT F5XDAXZQNZSV6ZRRNZSF63JTF4base32解码后是一个路由/n0_0ne_f1nd_m3/&#x…

数据仓库实验四:聚类分析实验

目录 一、实验目的二、实验内容和要求三、实验步骤1、建立数据表2、建立数据源视图3、建立挖掘结构Student.dmm4、部署项目并浏览结果5、挖掘模型预测 四、实验结果分析五、实验总结体会 一、实验目的 通过本实验,进一步理解基于划分的、基于层次的、基于密度的聚类…

Easy-poi 和 EasyExcel 选型

目录 共同点地址如何选 共同点 easy-poi 和 easyexcel 都是基于 apache poi 进行二次开发的,底层都是依赖的 apache poi使用简单,都可以通过简单的注解实现excel文件的导入导出 地址 esay poi 是一个开源的 excel,word 处理框架。链接 easy excel 是…

Xed编辑器开发第二期:使用Rust从0到1写一个文本编辑器

第三篇 这部分接着处理用户退出命令以及一些其他新功能; 3.1 使用CtrlQ退出 modifiers: event::KeyModifiers::CONTROL,使用CONTROL替换之前的NONE值即可; 3.2 重构键盘输入 让我们重构我们的代码,以便我们有一个用于低级按键读取的函数&…

《Rust奇幻之旅:从Java和C++开启》第1章Hello world 2/5

讲动人的故事,写懂人的代码 很多程序员都在自学Rust。 🤕但Rust的学习曲线是真的陡,让人有点儿怵头。 程序员工作压力大,能用来自学新东西的时间简直就是凤毛麟角。 📕目前,在豆瓣上有7本Rust入门同类书。它们虽有高分评价,但仍存在不足。 首先,就是它们介绍的Rust新…

什么是谷歌爬虫?

其实就是谷歌用来浏览网络信息的一个自动化程序,他们会在你的网站爬取,寻找和搜集信息,谷歌爬虫可以说决定着一个网站在谷歌的生死 谷歌爬虫的作用机制就在于发现新网站以及新网页,然后他会把网页的内容带回去,更新到…

PikaUnsafe upfileupload

1.client check 客户端检测,前端js检测,禁用js和修改后缀名即可。 php格式不能上传,我们修改后缀上传。 蚁剑成功连接。 2.MIME type 这个就是 content-type 规定上传类型,上面的方法也能成功,也可以修改 conten-ty…

面试框架【面试准备】

前言 2023-9-12 12:12:04 2023-09-14 16:13:04 公开发布于 2024-5-22 00:16:21 以下内容源自《【面试准备】》 仅供学习交流使用 版权 禁止其他平台发布时删除以下此话 本文首次发布于CSDN平台 作者是CSDN日星月云 博客主页是https://blog.csdn.net/qq_51625007 禁止其他平…

奇偶数递增递减-第13届蓝桥杯选拔赛Python真题精选

[导读]:超平老师的Scratch蓝桥杯真题解读系列在推出之后,受到了广大老师和家长的好评,非常感谢各位的认可和厚爱。作为回馈,超平老师计划推出《Python蓝桥杯真题解析100讲》,这是解读系列的第70讲。 奇偶数递增递减&a…

vite+ts+mock+vue-router+pinia实现vue的路由权限

0.权限管理 前端的权限管理主要分为如下: 接口权限路由权限菜单权限按钮权限 权限是对特定资源的访问许可,所谓权限控制,也就是确保用户只能访问到被分配的资源 1.项目搭建 创建vite项目 yarn create vite配置别名 npm install path -…

4. C++入门:内联函数、auto关键字、范围for及nullptr

内联函数 概念 以inline修饰的函数叫做内联函数,编译时C编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率 对比C的宏 C语言不足:宏 #define ADD(x, y) ((x)(y))int main() {int ret…

python实现520表白图案

今天是520哦,作为程序员有必要通过自己的专业知识来向你的爱人表达下你的爱意。那么python中怎么实现绘制520表白图案呢?这里给出方法: 1、使用图形库(如turtle) 使用turtle模块,你可以绘制各种形状和图案…

Docker 安装kingbase V8r6

下载 官网下载,注意:这里下载 Docker 版本v8r6 安装 # 导入镜像 docker load -i kingbase.tar# 重命名 docker tag [image-name]:[tag] [new-image-name]:[new-tag]# 删除 docker rmi [image-name]:[tag]# 创建容器 docker run -tid \ --privileged \…

【前端】使用 Canvas 实现贪吃蛇小游戏

使用 Canvas 实现贪吃蛇小游戏 在这篇博客中,我们将介绍如何使用 HTML5 Canvas 和 JavaScript 实现一个简单的贪吃蛇(Snake)小游戏。这个项目是一个基础的游戏开发练习,它可以帮助你理解如何在 Canvas 上绘图、如何处理用户输入以…

Android:OkHttp网络请求框架的使用

目录 一,OkHttp简介 二,OkHttp请求处理流程 三,OkHttp环境配置 四,OkHttp的使用 1.get网络请求 2.post上传表单数据 3.post上传json格式数据 4.文件上传 5.文件下载 一,OkHttp简介 OkHttp是square公司推出的一…

rust的版本问题,安装问题,下载问题

rust的版本、安装、下载问题 rust版本问题, 在使用rust的时候,应用rust的包,有时候包的使用和rust版本有关系。 error: failed to run custom build command for pear_codegen v0.1.2 Caused by: process didnt exit successfully: D:\rus…

Aspose.PDF功能演示:在 JavaScript 中将 TXT 转换为 PDF

您是否正在寻找一种在 JavaScript 项目中将纯文本文件从TXT无缝转换为PDF格式的方法?您来对地方了!无论您是要构建 Web 应用程序、创建生产力工具,还是只是希望简化工作流程,直接从 JavaScript 代码中将 TXT 转换为 PDF 的功能都可…