数据结构与算法——二叉树+带你实现表达式树(附源码)

简介: 📖作者介绍:22级树莓人(计算机专业),热爱编程<目前在c++阶段,因为最近参加新星计划算法赛道(白佬),所以加快了脚步,果然急迫感会增加动力>——目标Windows,MySQL,Qt,数据结构与算法,Linux,多线程,会持续分享学习成果和小项目的📖作者主页:king&南星📖专栏链接:数据结构🎉欢迎各位→点赞👏 + 收藏💞 + 留言🔔​💬总结:希望你看完之后,能对你有所帮助,不足请指正!共同学习交流 🐾————————————————版权声明:本文为CSDN博主「热爱编程的小K」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

9d47d307221344818bc1c6d99a6260b2.png

文章目录

👨‍🎓二叉树

🧑‍🎓一、概念及定义

⛵1、概念

⛵2、性质

👩‍🎓 二、结点的定义、链表应用、空节点的说明

⛵1、结点声明

⛵2、链表的应用

⛵ 3、空结点的说明及画图

🧑‍🏫三、表达式树——遍历

⛵1、表达式树引入与介绍

⛵2、中序遍历

⛵3、后序遍历

⛵4、先序遍历

⛵5、总结

⛵ 6、构建一颗表达式树

⛵A、第一步

⛵B、第二步

⛵C、第三步

⛵D、第四步

⛵E、第五步

⛵F、第六步

🧑‍⚖️四、查找节点

🧑‍🌾五、插入节点

👩‍🔧六、综合代码

👨‍🎓二叉树

🧑‍🎓一、概念及定义

⛵1、概念

二叉树是一棵树,其中每个结点的儿子都不能多于两个

如下图是由一个跟和两个子树组成的二叉树,且子树可能为空

b3021c80edba4bc79b67fc16e01f64d4.png

⛵2、性质

二叉树的一个性质是平均二叉树的深度要比N小得多。分析表明,这个平均深度为O(根号N),而对于特殊的二叉树,即二叉查找树,其深度的平均值为O(logN),不幸的是这个深度是可以大到N-1的,如下图所示

8e81d5ae65e149de87b7f67be61b12f4.png

👩‍🎓 二、结点的定义、链表应用、空节点的说明

⛵1、结点声明

因为一颗二叉树最多有两个儿子,所以我们可以用指针直接指向他们。树节点的声明在结构体是类似于双链表的声明,在声明中,一个节点就是由关键字信息加上两个指向其他节点的指针(Lift和Right)组成的结构

//叶子节点数据

#define EMPTY 6666

//是否要显示EMPTY  为1 不显示EMPTY  为0 显示EMPTY

#define NOTSHOWEMPTY  1

typedef struct Node

{

int data;

struct Node* pLift;

struct Node* pright;

}Node;

⛵2、链表的应用

在进行一次插入时需要调用malloc创建一个节点,节点可以在调用free删除后释放

Node* createNode(int newNodedata)  

{

Node* newNode = malloc(sizeof(Node));

if (NULL == newNode) return newNode;

newNode->data = newNodedata;

newNode->pLift = newNode->pright = NULL;

return newNode;

}

⛵ 3、空结点的说明及画图

我们可以用链表的知识用矩形框画出二叉树,但是,树一般画成圆圈并用一些直线连接起来,因为二叉树实际上就是图,当涉及树时,我们也不显示地画出NULL指针,因为具有N个节点的每一颗二叉树都需要N+1个NULL指针,我们在这里描述的二叉树是无序二叉树,叶子节点用EMPTY节点表示

🧑‍🏫三、表达式树——遍历

⛵1、表达式树引入与介绍

下图是一个表达式树,表达式树的树叶是操作数,比如常量或变量,而其他的节点为操作符。由于这里所有的操作都是二元的,因此这颗特定的树正好是二叉树,虽然这是最简单的情况,但是节点含有的儿子还是有可能多于两个的。一个节点也有可能只有一个儿子,如具有一目操作符的情况。可以将通过递归计算左子树和右子树所得到的值应用在根处的算符操作中而算出表达式树T的值。在本例中,左子树的值为a+(b*c),右子树的值为(((d*e)+f)*g),因此整颗表达式的值为a+(b*c)+(((d*e)+f)*g)

cd213d115ebf4cbe829e4aae24732866.png

这里我们先看看我们这里用的数据

7bcc4f1fdfb0412dbfde7382330f60f8.png

⛵2、中序遍历

我们可以通过递归产生一个带括号的左表达式,然后打印出在根处的运算符,然后再递归地产生一个带括号的右表达式而得到一个(对两个括号整体进行运算的)中缀表达式。这种一般的方法(左,根,右)被称为中序遍历

代码如下

void _midTravel(Node* root)  

{

if (NULL == root) return;

_midTravel(root->pLift);

#if NOTSHOWEMPTY

if (root->data != EMPTY)

#endif

 printf("%d ", root->data);

_midTravel(root->pright);

}

⛵3、后序遍历

就是递归打印出左子树,右子树,然后打印根节点,也就是后缀表达式,这种遍历一般称为后序遍历

代码如下

void _lstTravel(Node* root)  

{

if (NULL == root) return;

_lstTravel(root->pLift);

_lstTravel(root->pright);

#if NOTSHOWEMPTY

if (root->data != EMPTY)

#endif

 printf("%d ", root->data);

}

⛵4、先序遍历

先序遍历就是先打印出根节点,然后递归的打印出右子树和左子树,是一种前序记法

代码如下

void _preTravel(Node* root)  

{

if (NULL == root) return;

#if NOTSHOWEMPTY

if (root->data != EMPTY)

#endif

 printf("%d ", root->data);

_preTravel(root->pLift);

_preTravel(root->pright);

}

⛵5、总结

已知中序 和 先序 可以推导出树 知道后序

已知中序 和 后序 可以推导出树 知道先序

已知先序和后序,不能推导出树

这是因为只要知道先序和后序一个就可以知道根节点了,知道了中序就可以推导出左右子树了

⛵ 6、构建一颗表达式树

我们现在给出一种算法来把后缀表达式转换为表达式树。这种方法酷似后缀求值算法。一次一个符号地读入表达式,如果符号是操作数,那么我们就建立一个单节点树并将一个指向它的指针推入栈中。如果符号是操作符,那么我们就从栈中弹出指向两棵树T1和T2的两个指针(T1的先弹出)并形成一颗新的树,该树的根就是操作符,它的左右儿子分别指向T2和T1。然后将指向这棵树的指针压入栈中


来看一个例子,设输入为:ab+cde+**


⛵A、第一步

前两个符号是操作数,因此我们创建两颗单节点树并将指向它们的指针压入栈中


b92b665e7ac34eaa871726f987dba83b.png

⛵B、第二步

接着,读入“+”,因此弹出指向这两颗树的指针,一棵新的树新成,而将指向该树的指针压入栈中

330767d760fb421c81fa55abaca9f885.png

⛵C、第三步

然后,读入c、d、e,在每棵单节点树创建后,将指向对应的树的指针压入栈中

5891d0b7040f43c0a4222fbcf48fd224.png

⛵D、第四步

接下来读入“+”,因此两棵树合并

210940f1c07e4693a7a5c87b7ff4942b.png

⛵E、第五步

继续进行,读入“”,因此,弹出两个树指针并形成一颗新的树,“”是它的根

a0fda0a3cfe24efdb0109dde4fc35718.png

⛵F、第六步

最后,读入最后一个符号,两棵树合并,而指向最后的树的指针留在栈中

38027f61535942a78bc4eb7bf82051a7.png

🧑‍⚖️四、查找节点

Node* findNode(Node* root, int findData)  

{

if (NULL == root) return NULL;

if (findData = EMPTY) return NULL;

if (root->data == findData) return root;

Node* pTemp = findNode(root->pLift, findData);

if (pTemp) return pTemp;

return findNode(root->pright, findData);

}

🧑‍🌾五、插入节点

这里就体现了递推的大用处了,还有就是要传二级指针,因为要修改根节点

//插入(先序遍历)

bool inserData(Node** root, int insertdata)

{

if (NULL == root) return false;

//插入根节点

if( NULL == *root )

{

 *root = createNode(insertdata);

 return true;

}

if ((*root)->data == EMPTY) return false;

if (true == inserData(&((*root)->pLift), insertdata))

 return true;

else

 return inserData(&((*root)->pright), insertdata);

}

👩‍🔧六、综合代码

#include<stdio.h>

#include<string.h>

#include<stdlib.h>

#include<stdbool.h>

//叶子节点数据

#define EMPTY 6666

//是否要显示EMPTY  为1 不显示EMPTY  为0 显示EMPTY

#define NOTSHOWEMPTY  1

typedef struct Node

{

int data;

struct Node* pLift;

struct Node* pright;

}Node;

//创建节点函数

Node* createNode(int newNodedata);

//PRE:先序遍历,MID:中序遍历,LST:后序遍历

enum travelType { PRE, MID, LST };

//遍历

void Travel(Node* root, enum travelType type);

//先序遍历

void _preTravel(Node* root);

//中序遍历

void _midTravel(Node* root);

//后序遍历

void _lstTravel(Node* root);

//插入(先序遍历)

bool inserData(Node** root, int insertdata);

//找到数据为findData的第一个节点 找到返回节点地址 否则返回NULL

Node* findNode(Node* root, int findData);

int main()  

{

//一颗空树

Node* pRoot = NULL;

int arr[] = { 10, 99, 83, EMPTY, 22, EMPTY, EMPTY, EMPTY ,96,

 EMPTY, 56, 6, EMPTY, EMPTY, 11, 36, EMPTY, EMPTY, EMPTY,666 };

for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)

 inserData(&pRoot, arr[i]);

Travel(pRoot, PRE);

Travel(pRoot, MID);

Travel(pRoot, LST);

return 0;

}

//创建节点函数

Node* createNode(int newNodedata)  

{

Node* newNode = malloc(sizeof(Node));

if (NULL == newNode) return newNode;

newNode->data = newNodedata;

newNode->pLift = newNode->pright = NULL;

return newNode;

}

//遍历

void Travel(Node* root, enum travelType type)  

{

switch (type)

{

case PRE:

 printf("先序遍历:");

 _preTravel(root);

 printf("\n");

 break;

case MID:

 printf("中序遍历:");

 _midTravel(root);

 printf("\n");

 break;

case LST:

 printf("后序遍历: ");

 _lstTravel(root);

 printf("\n");

 break;

}

}

//先序遍历

void _preTravel(Node* root)  

{

if (NULL == root) return;

#if NOTSHOWEMPTY

if (root->data != EMPTY)

#endif

 printf("%d ", root->data);

_preTravel(root->pLift);

_preTravel(root->pright);

}

//中序遍历

void _midTravel(Node* root)  

{

if (NULL == root) return;

_midTravel(root->pLift);

#if NOTSHOWEMPTY

if (root->data != EMPTY)

#endif

 printf("%d ", root->data);

_midTravel(root->pright);

}

//后序遍历

void _lstTravel(Node* root)  

{

if (NULL == root) return;

_lstTravel(root->pLift);

_lstTravel(root->pright);

#if NOTSHOWEMPTY

if (root->data != EMPTY)

#endif

 printf("%d ", root->data);

}

//插入(先序遍历)

bool inserData(Node** root, int insertdata)

{

if (NULL == root) return false;

//插入根节点

if( NULL == *root )

{

 *root = createNode(insertdata);

 return true;

}

if ((*root)->data == EMPTY) return false;

if (true == inserData(&((*root)->pLift), insertdata))

 return true;

else

 return inserData(&((*root)->pright), insertdata);

}

//找到数据为findData的第一个节点 找到返回节点地址 否则返回NULL

Node* findNode(Node* root, int findData)  

{

if (NULL == root) return NULL;

if (findData = EMPTY) return NULL;

if (root->data == findData) return root;

Node* pTemp = findNode(root->pLift, findData);

if (pTemp) return pTemp;

return findNode(root->pright, findData);

}

版权声明:本文为CSDN博主「热爱编程的小K」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/qq_72157449/article/details/129700648

相关文章
|
10天前
|
存储 算法 Java
算法系列之数据结构-二叉树
树是一种重要的非线性数据结构,广泛应用于各种算法和应用中。本文介绍了树的基本概念、常见类型(如二叉树、满二叉树、完全二叉树、平衡二叉树、B树等)及其在Java中的实现。通过递归方法实现了二叉树的前序、中序、后序和层次遍历,并展示了具体的代码示例和运行结果。掌握树结构有助于提高编程能力,优化算法设计。
42 9
 算法系列之数据结构-二叉树
|
7天前
|
算法 Java
算法系列之数据结构-Huffman树
Huffman树(哈夫曼树)又称最优二叉树,是一种带权路径长度最短的二叉树,常用于信息传输、数据压缩等方面。它的构造基于字符出现的频率,通过将频率较低的字符组合在一起,最终形成一棵树。在Huffman树中,每个叶节点代表一个字符,而每个字符的编码则是从根节点到叶节点的路径所对应的二进制序列。
32 3
 算法系列之数据结构-Huffman树
|
2天前
|
存储 自然语言处理 数据库
【数据结构进阶】AVL树深度剖析 + 实现(附源码)
在深入探讨了AVL树的原理和实现后,我们不难发现,这种数据结构不仅优雅地解决了传统二叉搜索树可能面临的性能退化问题,还通过其独特的平衡机制,确保了在任何情况下都能提供稳定且高效的查找、插入和删除操作。
35 19
|
2月前
|
存储 C++
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
【数据结构——树】哈夫曼树(头歌实践教学平台习题)【合集】目录 任务描述 相关知识 测试说明 我的通关代码: 测试结果:任务描述 本关任务:编写一个程序构建哈夫曼树和生成哈夫曼编码。 相关知识 为了完成本关任务,你需要掌握: 1.如何构建哈夫曼树, 2.如何生成哈夫曼编码。 测试说明 平台会对你编写的代码进行测试: 测试输入: 1192677541518462450242195190181174157138124123 (用户分别输入所列单词的频度) 预
72 14
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
|
2月前
|
Java C++
【C++数据结构——树】二叉树的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现二叉树的基本运算。​ 相关知识 创建二叉树 销毁二叉树 查找结点 求二叉树的高度 输出二叉树 //二叉树节点结构体定义 structTreeNode{ intval; TreeNode*left; TreeNode*right; TreeNode(intx):val(x),left(NULL),right(NULL){} }; 创建二叉树 //创建二叉树函数(简单示例,手动构建) TreeNode*create
61 12
|
2月前
|
C++
【C++数据结构——树】二叉树的性质(头歌实践教学平台习题)【合集】
本文档介绍了如何根据二叉树的括号表示串创建二叉树,并计算其结点个数、叶子结点个数、某结点的层次和二叉树的宽度。主要内容包括: 1. **定义二叉树节点结构体**:定义了包含节点值、左子节点指针和右子节点指针的结构体。 2. **实现构建二叉树的函数**:通过解析括号表示串,递归地构建二叉树的各个节点及其子树。 3. **使用示例**:展示了如何调用 `buildTree` 函数构建二叉树并进行简单验证。 4. **计算二叉树属性**: - 计算二叉树节点个数。 - 计算二叉树叶子节点个数。 - 计算某节点的层次。 - 计算二叉树的宽度。 最后,提供了测试说明及通关代
60 10
|
5月前
|
存储 人工智能 算法
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
135 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
|
5月前
|
机器学习/深度学习 存储 缓存
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
文章主要介绍了排序算法的分类、时间复杂度的概念和计算方法,以及常见的时间复杂度级别,并简单提及了空间复杂度。
110 1
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
|
5月前
|
搜索推荐 算法
数据结构与算法学习十四:常用排序算法总结和对比
关于常用排序算法的总结和对比,包括稳定性、内排序、外排序、时间复杂度和空间复杂度等术语的解释。
67 0
数据结构与算法学习十四:常用排序算法总结和对比
|
5月前
|
存储 缓存 分布式计算
数据结构与算法学习一:学习前的准备,数据结构的分类,数据结构与算法的关系,实际编程中遇到的问题,几个经典算法问题
这篇文章是关于数据结构与算法的学习指南,涵盖了数据结构的分类、数据结构与算法的关系、实际编程中遇到的问题以及几个经典的算法面试题。
69 0
数据结构与算法学习一:学习前的准备,数据结构的分类,数据结构与算法的关系,实际编程中遇到的问题,几个经典算法问题