【数据结构】二叉树链式结构的实现
前置声明:在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在大家对二叉树结构掌握还不够深入,为了降低大家学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。
typedef int BTDataType;
typedef struct BinaryTreeBNode
{BTDataType data;struct BinaryTreeBNode* left;struct BinaryTreeBNode* right;
}BTNode;BTNode* BuyNode(int x)
{BTNode* node = (BTNode*)malloc(sizeof(BTNode));if (node == NULL){perror("malloc fail");return NULL;}node->data = x;node->left = NULL;node->right = NULL;return node;
}BTNode* CratBinaryTree()
{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;
}
注意:上述并不是创建二叉树的方式,真正创建二叉树的方式期待后续博客
再看二叉树基本操作前,再回顾下二叉树的概念,二叉树是:
1. 空树
2. 非空:根结点,根结点的左子树、根结点的右子树组成的。

从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。
1.前序遍历
学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉 树中的结点进行相应的操作,并且每个结点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历 是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。

我们先来看看前序遍历:
前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。简单来说遍历的顺序为:根 左子树 右子树
递归的本质:拆成把当前问题和子问题,返回条件:最小规模的子问题
我们可以把大问题化成小问题,首先访问根然后访问左子树那么左子树又可以拆成根左子树右子树,结束条件就是树为空。
代码如下:
//前序
void PrevOrder(BTNode* root)
{if (root == NULL){printf("N");return;}printf("%d ", root->data);PrevOrder(root->left);PrevOrder(root->right);
}


如图可以看出:
前序遍历的结果为:1 2 3 4 5 6
加上空树为(NULL简写成N):1 2 3 N N N 4 5 N N 6 N N
前序遍历递归图解:

注意:任何一棵树的访问都要符合前序,拆成根->左子->树右子树
2.中序遍历
中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
中序遍历的访问顺序为:左子树 根 右子树
//中序
void InOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}InOrder(root->left);printf("%d ", root->data);InOrder(root->right);}
中序遍历的结果如下:
![]()
递归展开图如下:

3.后序遍历
后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。
后序遍历的访问顺序为:左子树 右子树 根
//后序
void Postorder(BTNode* root)
{if (root == NULL){printf("N ");return;}Postorder(root->left);Postorder(root->right);printf("%d ", root->data);
}
后序遍历的结果如下:

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

层序遍历的代码是如何实现的呢?
这里要用到我们之前学的队列,我们让根节点先入队列,再让根节点出队列出队列的同时根的左子树和右子树入队,前提是左右孩子节点不为空,以此内推,直到队列为空时循环停止。
代码如下:
// 层序遍历
void TreeLevelOrder(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);}QueueDestroy(&q);
}
5.二叉树节点个数
我们如何用代码统计二叉树的节点个数呢,我们知道每棵树都有左子树和右子树,如果左子树为空就说明左边没有节点我们就返回0给上一层,右子树如果为空那就说明右边没有节点我们也返回0给上一层,不为空就返回左子树+右子树+1也就是加上自身节点,我们使用递归,为了节省效率,可以用三目操作符。
代码如下:
//节点个数
int TreeSize(BTNode* root)
{return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;}
6.二叉树叶子节点个数
如果是空树就返回0,如果左子树和右子树为空就是叶子节点那么就返回1,统计左子树和右子树的节点即可。
代码如下:
//求叶子节点
int TreeLeafSize(BTNode* root)
{if (root == NULL)return 0;if (root->left == NULL && root->right == NULL)return 1;return TreeLeafSize(root->left)+ TreeLeafSize(root->right);
}
7.求二叉树的高度
一棵树的高度等于左子树和右子树深度较高的那个+1,通过不断的递归找到最高的深度,空树则返回0。
//求树的高度(深度)
int TreeHeight(BTNode* root)
{if (root == NULL)return 0;int leftHeight = TreeHeight(root->left);int rightHeight = TreeHeight(root->right);return leftHeight > rightHeight ?leftHeight + 1 : rightHeight + 1;
}
8.二叉树第K层节点个数
我们可以拆成子问题来解决,每一层是下一层的K-1层,当K=1的时候说明已经到第K层了就返回1
//二叉树第K层节点个数
int TreeLevelKSize(BTNode* root, int k)
{if (root == NULL)return 0;if (k == 1)return 1;//子问题return TreeLevelKSize(root->left, k - 1) + TreeLevelKSize(root->right, k - 1);
}

9.二叉树查找值为x的节点
查找x的节点我们可以先序遍历一下,如果找到值为x的节点则用临时变量保存下来不能直接返回,因为返回是返回给递归调用的上一层并不能完全返回出来,所以需要使用临时变量保存一下。
//二叉树查找值为x的节点
BTNode* TreeFind(BTNode* root, BTDataType x)
{if (root == NULL)return NULL;if (root->data == x)return root;BTNode* ret1 = TreeFind(root->left, x);if (ret1)return ret1;BTNode* ret2 = TreeFind(root->right, x);if (ret2)return ret2;return NULL;
}
10. 判断二叉树是否是完全二叉树
判断是否是完全二叉树我们可以使用层序遍历,完全二叉树中最后一层非空和空一定是连续的,这里我们不管是非空还是空都入队列,出队列的时候如果遇见第一个为空的就要开始判断队列中是否还有非空的如有则不是完全二叉树否则为完全二叉树。

代码如下:
// 判断二叉树是否是完全二叉树
bool TreeComplete(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;
}
11.二叉树销毁
二叉树的销毁我们通过后序遍历来销毁,这里不能使用前序来销毁因为销毁了根节点就不能找到左子树和右子树了,中序也是一样的道理

代码如下:
// 二叉树销毁
void TreeDestory(BTNode* root)
{if (root == NULL)return;TreeDestory(root->left);TreeDestory(root->right);free(root);}
12.以下是二叉树实现的所有代码
.h
#pragma once
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
typedef int BTDataType;
typedef struct BinaryTreeBNode
{BTDataType data;struct BinaryTreeBNode* left;struct BinaryTreeBNode* right;
}BTNode;BTNode* BuyNode(int x);
void PrevOrder(BTNode* root);
void InOrder(BTNode* root);
void Postorder(BTNode* root);
//节点个数
int TreeSize(BTNode* root);
//求叶子节点
int TreeLeafSize(BTNode* root);
//求树的高度(深度)
int TreeHeight(BTNode* root);
//二叉树第K层节点个数
int TreeLevelKSize(BTNode* root, int k);
//二叉树查找值为x的节点
BTNode* TreeFind(BTNode* root, BTDataType x);// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi);// 层序遍历
void TreeLevelOrder(BTNode* root);// 判断二叉树是否是完全二叉树
bool TreeComplete(BTNode* root);// 二叉树销毁
void TreeDestory(BTNode* root);
.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "Tree.h"
#include "Queue.h"
BTNode* BuyNode(int x)
{BTNode* node = (BTNode*)malloc(sizeof(BTNode));if (node == NULL){perror("malloc fail");return NULL;}node->data = x;node->left = NULL;node->right = NULL;return node;
}
//前序
void PrevOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}printf("%d ", root->data);PrevOrder(root->left);PrevOrder(root->right);
}
//中序
void InOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}InOrder(root->left);printf("%d ", root->data);InOrder(root->right);}
//后序
void Postorder(BTNode* root)
{if (root == NULL){printf("N ");return;}Postorder(root->left);Postorder(root->right);printf("%d ", root->data);
}//节点个数
int TreeSize(BTNode* root)
{return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;}
//求叶子节点
int TreeLeafSize(BTNode* root)
{if (root == NULL)return 0;if (root->left == NULL && root->right == NULL)return 1;return TreeLeafSize(root->left)+ TreeLeafSize(root->right);
}
//求树的高度(深度)
int TreeHeight(BTNode* root)
{if (root == NULL)return 0;int leftHeight = TreeHeight(root->left);int rightHeight = TreeHeight(root->right);return leftHeight > rightHeight ?leftHeight + 1 : rightHeight + 1;
}//二叉树第K层节点个数
int TreeLevelKSize(BTNode* root, int k)
{if (root == NULL)return 0;if (k == 1)return 1;//子问题return TreeLevelKSize(root->left, k - 1) + TreeLevelKSize(root->right, k - 1);
}
//二叉树查找值为x的节点
BTNode* TreeFind(BTNode* root, BTDataType x)
{if (root == NULL)return NULL;if (root->data == x)return root;BTNode* ret1 = TreeFind(root->left, x);if (ret1)return ret1;BTNode* ret2 = TreeFind(root->right, x);if (ret2)return ret2;return NULL;
}// 二叉树销毁
void TreeDestory(BTNode* root)
{if (root == NULL)return;TreeDestory(root->left);TreeDestory(root->right);free(root);}
// 层序遍历
void TreeLevelOrder(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);}QueueDestroy(&q);
}// 判断二叉树是否是完全二叉树
bool TreeComplete(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;
}
.test
#define _CRT_SECURE_NO_WARNINGS 1
#include "Tree.h"
#include "Queue.h"
BTNode* CratBinaryTree()
{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;
}
int main()
{BTNode*root = CratBinaryTree();//前序PrevOrder(root);printf("\n");//中序InOrder(root);printf("\n");//后序Postorder(root);printf("\n");//二叉树第K层节点个数int ret = TreeLevelKSize(root,3); printf("TreeLevelKSize:%d \n", ret);//二叉树查找值为x的节点BTNode* ret1 = TreeFind(root, 3);printf("TreeFind:%d \n", root->data);printf("TreeSize:%d\n", TreeSize(root));printf("TreeLeafSize:%d\n", TreeLeafSize(root));printf("TreeHeight:%d\n", TreeHeight(root));//层序遍历TreeLevelOrder(root);printf("\n");//判断是否是完全二叉树bool ret4 = TreeComplete(root);printf("%d ", ret4);TreeDestory(root);root = NULL;return 0;
}
相关文章:
【数据结构】二叉树链式结构的实现
前置声明:在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在大家对二叉树结构掌握还不够深入,为了降低大家学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉…...
如何有效找到目标客户群体?
在激烈的市场竞争中,找到并锁定目标客户群体是企业成功的关键。以下是几种有效的策略,帮助您精准定位并吸引目标客户。 1. 明确市场定位与客户画像 首先,企业需要明确市场定位,并绘制详细的客户画像,包括年龄、性别、…...
机器学习-混淆矩阵
文章目录 一、混淆矩阵1.混淆矩阵简介2.混淆矩阵图列 二、混淆矩阵指标1. 准确率(Accuracy)2. 精确率(Precision)3. 召回率(Recall)4. F1分数(F1 Score) 三、总结 一、混淆矩阵 1.混…...
数据结构----栈
一丶概念 只能在一端进行插入和删除操作的线性表(又称为堆栈),进行插入和删除操作的一端称为栈顶,另一端称为栈底 二丶特点 先进后出 FILO first in last out 后进先出 LIFO last in first out 三丶顺序栈 逻辑结构&…...
STL六大组件
STL(Standard Template Library,标准模板库)是C标准库的一部分,提供了丰富且高效的数据结构和算法。STL主要由6大组件构成,分别是容器、算法、迭代器、适配器、仿函数和空间配置器。 容器(Containers&#…...
【机器学习】CNN的数学基础
🌈个人主页: 鑫宝Code 🔥热门专栏: 闲话杂谈| 炫酷HTML | JavaScript基础 💫个人格言: "如无必要,勿增实体" 文章目录 CNN的数学基础1. 引言2. 卷积运算2.1 连续卷积2.2 离散卷积2.3 互相关 3. 激活函…...
最小路径和[中等]
优质博文:IT-BLOG-CN 一、题目 给定一个包含非负整数的m x n网格grid,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 说明:每次只能向下或者向右移动一步。 示例 1: 输入:grid [[…...
【题库】——数组 小鱼比可爱
#include<bits/stdc.h> using namespace std; int main() {int n,m,i;cin>>n;int arr[n]; for(i0;i<n;i) {int count 0;cin>>arr[i];for(mi;m>0;m--){if(arr[i]>arr[m])count;} cout<<count<<" "; } return 0; }...
基于飞腾平台的Hbase的安装配置
【写在前面】 飞腾开发者平台是基于飞腾自身强大的技术基础和开放能力,聚合行业内优秀资源而打造的。该平台覆盖了操作系统、算法、数据库、安全、平台工具、虚拟化、存储、网络、固件等多个前沿技术领域,包含了应用使能套件、软件仓库、软件支持、软件适…...
【springboot】springboot接口参数全局解密,解决request内容修改后如何重新设置回去的问题
文章目录 核心思路spring&servelt基础核心接口类核心代码 body解密核心原理讲解get解密核心原理讲解get query请求讲解get pathVariables请求讲解 总结 本文不仅介绍了body内容修改后如何传递,也介绍了get请求 在修改内容后如何继续传递。 【原创作者 csdn: 孟秋…...
yml基本语法
YAML(YAML Ain’t Markup Language)是一种简洁且易读的数据序列化格式,常用于配置文件。Spring Boot 中的 application.yml 文件使用 YAML 来配置应用程序的属性。 YAML 基本语法 1. 键值对 基本的键值对表示形式为:key: value…...
橙色简洁大气体育直播自适应模板赛事直播门户自适应网站源码
源码名称:酷黑简洁大气体育直播自适应模板赛事直播门户网站 源码开发环境:帝国cms 7.5 安装环境:phpmysql 带采集,可以挂着电脑上自动采集发布,无需人工操作! 橙色简洁大气体育直播自适应模板赛事直播门户…...
【启明智显技术分享】工业级HMI芯片Model系列GUI合成到项目中的指南
在工业自动化、智能终端HMI、车载仪表盘等领域,高性能的HMI(人机界面)芯片是不可或缺的核心组件。启明智显推出的Model系列(如Model3C、Model3、Model4)HMI芯片,以其卓越的性能和广泛的应用领域,…...
开源服务器运维工具1Panel
1Panel是杭州飞致云信息科技有限公司推出的一款现代化、开源的Linux服务器运维管理面板。 以下是对1Panel的详细介绍: 一、基本信息 产品名称:1Panel所属公司:杭州飞致云信息科技有限公司编写语言:Golang上线时间:20…...
新版本源2.0大模型发布:Yuan2-2B-July-hf
引言 近日,浪潮信息的新一代基础语言大模型源2.0 迎来了重要更新。浪潮信息正式发布了 Yuan2-2B-July-hf 模型,标志着源2.0系列模型在性能和功能上的进一步提升。这一版本将为开发者和研究人员提供更强大的工具,以满足各种语言处理需求。…...
用python生成GIF动图—用于博客插图或封面等
生成GIF动图🚀 由于目前自己是在做大模型,还有一些树莓派硬件之类的东西,一是大模型的流式输出的例子需要用到GIF,二是做单片机的时候例如一些灯的闪烁和变化需要用到,所以之前也是一直有这个打算所以就记录一下这个生…...
[RCTF2019]draw
下载是一个文本文档,百度AI cs pu lt 90 fd 500 rt 90 pd fd 100 rt 90 repeat 18[fd 5 rt 10] lt 135 fd 50 lt 135 pu bk 100 pd setcolor pick [ red orange yellow green blue violet ] repeat 18[fd 5 rt 10] rt 90 fd 60 rt 90 bk 30 rt 90 fd 60 pu lt 90 f…...
设计模式 - 责任链模式
💝💝💝首先,欢迎各位来到我的博客!本文深入理解设计模式原理、应用技巧、强调实战操作,提供代码示例和解决方案,适合有一定编程基础并希望提升设计能力的开发者,帮助读者快速掌握并灵活运用设计模式。 💝💝💝如有需要请大家订阅我的专栏【设计模式】哟!我会定…...
jpg怎么转换成pdf?6个简单方法,实现jpg转换成pdf
你是否也曾想将jpg图片转换为pdf格式文档呢?亦或者在处理文档或制作报告时,不知道怎么才能更快地将多张图片整合成一个pdf文件呢?如果你正在寻找简单快速的方法,又有哪些工具可以帮助您完成图片转pdf呢?别着急…...
ptrade排坑笔记——使用量化交易的时候有报错提示!
前言 今天要和大家分享一个遇见的问题,有客户反馈,自己在使用量化交易的时候,会有报错!会在后文分享我们是如何解决这个问腿的! 一、问题描述 客户主要遇见的问题是,量化在进行交易的过程中,…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
重启Eureka集群中的节点,对已经注册的服务有什么影响
先看答案,如果正确地操作,重启Eureka集群中的节点,对已经注册的服务影响非常小,甚至可以做到无感知。 但如果操作不当,可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...
C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...
