【数据结构】二叉树链式结构的实现
前置声明:在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在大家对二叉树结构掌握还不够深入,为了降低大家学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。
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排坑笔记——使用量化交易的时候有报错提示!
前言 今天要和大家分享一个遇见的问题,有客户反馈,自己在使用量化交易的时候,会有报错!会在后文分享我们是如何解决这个问腿的! 一、问题描述 客户主要遇见的问题是,量化在进行交易的过程中,…...
测试微信模版消息推送
进入“开发接口管理”--“公众平台测试账号”,无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息: 关注测试号:扫二维码关注测试号。 发送模版消息: import requests da…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...
React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...
智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...
3-11单元格区域边界定位(End属性)学习笔记
返回一个Range 对象,只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意:它移动的位置必须是相连的有内容的单元格…...
如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
React---day11
14.4 react-redux第三方库 提供connect、thunk之类的函数 以获取一个banner数据为例子 store: 我们在使用异步的时候理应是要使用中间件的,但是configureStore 已经自动集成了 redux-thunk,注意action里面要返回函数 import { configureS…...
WebRTC从入门到实践 - 零基础教程
WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC? WebRTC(Web Real-Time Communication)是一个支持网页浏览器进行实时语音…...
libfmt: 现代C++的格式化工具库介绍与酷炫功能
libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库,提供了高效、安全的文本格式化功能,是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全:…...
