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

在这里插入图片描述

📖作者介绍:22级树莓人(计算机专业),热爱编程<目前在c++阶段,因为最近参加新星计划算法赛道(白佬),所以加快了脚步,果然急迫感会增加动力>——目标Windows,MySQL,Qt,数据结构与算法,Linux,多线程,会持续分享学习成果和小项目的
📖作者主页:king&南星
📖专栏链接:数据结构

🎉欢迎各位→点赞👏 + 收藏💞 + 留言🔔​
💬总结:希望你看完之后,能对你有所帮助,不足请指正!共同学习交流 🐾

在这里插入图片描述


👨‍🎓二叉树

🧑‍🎓一、概念及定义

⛵1、概念

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

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

在这里插入图片描述

⛵2、性质

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

在这里插入图片描述

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

⛵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)

在这里插入图片描述

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

在这里插入图片描述

⛵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、第一步

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

在这里插入图片描述

⛵B、第二步

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

在这里插入图片描述

⛵C、第三步

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

在这里插入图片描述

⛵D、第四步

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

在这里插入图片描述

⛵E、第五步

继续进行,读入“”,因此,弹出两个树指针并形成一颗新的树,“”是它的根
在这里插入图片描述

⛵F、第六步

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

在这里插入图片描述

🧑‍⚖️四、查找节点

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);
}
  • 78
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 118
    评论
http://www.400gb.com/u/256394 译者序 前言 致谢 第1章 绪论  1.1 评估算法  1.2 修改算法   1.2.1 主要的优化:I/O   1.2.2 主要的优化:函数调用  1.3 资和参考资料 第2章 基本数据结构  2.1 链表   2.1.1 双向链表   2.1.2 链表的其他特征  2.2 栈和队列   2.2.1 栈的特征   2.2.2 队列的特征 第3章 散列  3.1 散列的概念  3.2 散列函数  3.3 冲突解决方法   3.3.1 线性再散列法   3.3.2 非线性再散列法   3.3.3 外部拉链法  3.4 性能问题  3.5 资和参考资料 第4章 查找  4.1 查找的特征   4.1.1 准备时间   4.1.2 运行时间     4.1.3 回溯的需要  4.2 蛮力查找  4.3 Boyer Moore查找   4.3.1 启发式方法#1:跳过字符   4.3.2 启发式方法#2:重复模式  4.4 多字符串查找  4.5 用于正则表达式的字符串查找:grep  4.6 近似字符串匹配技术  4.7 语音比较:Soundex算法  4.8 Metaphone:现代的Soundex  4.9 选择技术  4.10 资和参考资料   4.10.1 通用参考资料   4.10.2 Boyer Moore   4.10.3 多字符串查找   4.10.4 正则表达式查找   4.10.5 近似字符串匹配   4.10.6 Soundex算法和Metaphone算法 第5章 排序  5.1 排序的基本特征   5.1.1 稳定性   5.1.2 对哨兵的需求   5.1.3 对链表进行排序的能力   5.1.4 输入的阶的相关性   5.1.5 对额外存储空间的需求   5.1.6 内部排序技术与外部排序技术  5.2 排序模型   5.2.1 冒泡排序   5.2.2 插入排序   5.2.3 希尔排序   5.2.4 快速排序   5.2.5 堆排序  5.3 对链表进行插入排序  5.4 对链表进行快速排序  5.5 对多个键进行排序——不稳定排序的修正方法  5.6 网络排序  5.7 小结:选择一种排序算法  5.8 资和参考资料 第6章   6.1 二叉树   6.1.1 查找   6.1.2 节点插入   6.1.3 节点删除   6.1.4 二叉查找的性能   6.1.5 AVL  6.2 红黑  6.3 伸展  6.4 B   6.4.1 保持B平衡   6.4.2 实现B算法   6.4.3 B实现的代码  6.5 可以看见森林吗  6.6 资和参考资料 第7章 日期和时间  7.1 日期例程的库  7.2 时间例程  7.3 用于日期和时间数据的格式  7.4 最后的提醒  7.5 资和参考资料 第8章 任意精度的算术  8.1 构建计算器8.2表示数字  8.3 计算  8.4 加法  8.5 减法  8.6 乘法  8.7 除法  8.8 关于计算器要注意的最后几点  8.9 用于计算平方根的牛顿算法  8.10 分期付款表  8.11 资和参考资料 第9章 数据压缩  9.1 行程编码  9.2 霍夫曼压缩   9.2.1 代码   9.2.2 其他问题  9.3 滑动窗口压缩  9.4 基于字典的压缩(LZW)   9.4.1 LZW算法的伪代码   9.4.2 LZW压缩的实现   9.4.3 填满字典  9.5 使用哪种压缩方法  9.6 资和参考资料 第10章 数据完整性和验证  10.1 简单的校验和  10.2 加权校验和  10.3 循环冗余校验   10.3.1 CRC CCITT   10.3.2 CRC 16   10.3.3 CRC 32   10.4 资和参考资料
1. 什么是二叉树? 二叉树是一种形结构,其中每个节点最多有两个子节点。一个节点的左子节点比该节点小,右子节点比该节点大。二叉树通常用于搜索和排序。 2. 二叉树的遍历方法有哪些? 二叉树的遍历方法包括前序遍历、中序遍历和后序遍历。前序遍历是从根节点开始遍历,先访问根节点,再访问左子,最后访问右子。中序遍历是从根节点开始遍历,先访问左子,再访问根节点,最后访问右子。后序遍历是从根节点开始遍历,先访问左子,再访问右子,最后访问根节点。 3. 二叉树的查找方法有哪些? 二叉树的查找方法包括递归查找和非递归查找。递归查找是从根节点开始查找,如果当前节点的值等于要查找的值,则返回当前节点。如果要查找的值比当前节点小,则继续在左子中查找;如果要查找的值比当前节点大,则继续在右子中查找。非递归查找可以使用栈或队列实现,从根节点开始,每次将当前节点的左右子节点入栈/队列,直到找到要查找的值或者栈/队列为空。 4. 二叉树的插入与删除操作如何实现? 二叉树的插入操作是将要插入的节点与当前节点的值进行比较,如果小于当前节点的值,则继续在左子中插入;如果大于当前节点的值,则继续在右子中插入。当找到一个空节点时,就将要插入的节点作为该空节点的子节点。删除操作需要分为三种情况:删除叶子节点、删除只有一个子节点的节点和删除有两个子节点的节点。删除叶子节点很简单,只需要将其父节点的对应子节点置为空即可。删除只有一个子节点的节点,需要将其子节点替换为该节点的位置。删除有两个子节点的节点,则可以找到该节点的后继节点(即右子中最小的节点),将其替换为该节点,然后删除后继节点。 5. 什么是平衡二叉树? 平衡二叉树是一种特殊的二叉树,它保证左右子的高度差不超过1。这种平衡可以确保二叉树的查找、插入和删除操作的时间复杂度都是O(logn)。常见的平衡二叉树包括红黑和AVL

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 118
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

热爱编程的张同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值