数据结构与算法——二叉树+带你实现表达式树(附源码)
创始人
2025-06-01 06:40:54
0

在这里插入图片描述

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

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

在这里插入图片描述

文章目录

    • 👨‍🎓二叉树
      • 🧑‍🎓一、概念及定义
        • ⛵1、概念
        • ⛵2、性质
      • 👩‍🎓 二、结点的定义、链表应用、空节点的说明
        • ⛵1、结点声明
        • ⛵2、链表的应用
        • ⛵ 3、空结点的说明及画图
      • 🧑‍🏫三、表达式树——遍历
        • ⛵1、表达式树引入与介绍
        • ⛵2、中序遍历
        • ⛵3、后序遍历
        • ⛵4、先序遍历
        • ⛵5、总结
        • ⛵ 6、构建一颗表达式树
          • ⛵A、第一步
          • ⛵B、第二步
          • ⛵C、第三步
          • ⛵D、第四步
          • ⛵E、第五步
          • ⛵F、第六步
      • 🧑‍⚖️四、查找节点
      • 🧑‍🌾五、插入节点
      • 👩‍🔧六、综合代码


👨‍🎓二叉树

🧑‍🎓一、概念及定义

⛵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 NOTSHOWEMPTYif (root->data != EMPTY)
#endifprintf("%d ", root->data);_midTravel(root->pright);
}

⛵3、后序遍历

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

代码如下

void _lstTravel(Node* root) 
{if (NULL == root) return;_lstTravel(root->pLift);_lstTravel(root->pright);
#if NOTSHOWEMPTYif (root->data != EMPTY)
#endifprintf("%d ", root->data);
}

⛵4、先序遍历

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

代码如下

void _preTravel(Node* root) 
{if (NULL == root) return;
#if NOTSHOWEMPTYif (root->data != EMPTY)
#endifprintf("%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;elsereturn inserData(&((*root)->pright), insertdata);
}

👩‍🔧六、综合代码

#include
#include
#include
#include
//叶子节点数据
#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 NOTSHOWEMPTYif (root->data != EMPTY)
#endifprintf("%d ", root->data);_preTravel(root->pLift);_preTravel(root->pright);
}
//中序遍历
void _midTravel(Node* root) 
{if (NULL == root) return;_midTravel(root->pLift);
#if NOTSHOWEMPTYif (root->data != EMPTY)
#endifprintf("%d ", root->data);_midTravel(root->pright);
}
//后序遍历
void _lstTravel(Node* root) 
{if (NULL == root) return;_lstTravel(root->pLift);_lstTravel(root->pright);
#if NOTSHOWEMPTYif (root->data != EMPTY)
#endifprintf("%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;elsereturn 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);
}

相关内容

热门资讯

甜城麻将安卓系统,解锁全新麻将... 你有没有听说过那个超级火的甜城麻将安卓系统?没错,就是那个让无数麻将爱好者为之疯狂的软件!今天,就让...
安卓系统卸载的软件,深度揭秘卸... 手机里的软件越来越多,是不是感觉内存不够用了?别急,今天就来教你怎么在安卓系统里卸载那些不再需要的软...
安卓系统推荐好游戏,畅享指尖乐... 手机里的游戏可是咱们休闲娱乐的好伙伴,尤其是安卓系统的用户,选择面那可是相当广呢!今天,就让我来给你...
王者安卓系统怎么卖,揭秘如何轻... 你有没有听说最近王者安卓系统的火爆程度?没错,就是那个让无数玩家沉迷其中的王者荣耀!今天,我就来给你...
安卓开发系统内置证书,基于安卓... 你有没有想过,你的安卓手机里那些神秘的内置证书,它们到底是个啥玩意儿?别急,今天就来给你揭秘这些隐藏...
荣耀安装安卓原生系统,深度体验... 你知道吗?最近荣耀手机界可是掀起了一股热潮,那就是——荣耀安装安卓原生系统!这可不是什么小打小闹,而...
安卓13小米系统,创新功能与流... 你知道吗?最近安卓13系统可谓是风头无两,各大手机厂商纷纷推出自家的新版系统,其中小米的安卓13系统...
鸿蒙系统底层安卓10,融合与创... 你知道吗?最近手机圈里可是热闹非凡呢!华为的新操作系统鸿蒙系统,竟然在底层采用了安卓10的架构。这可...
安卓系统辅助在哪关闭,轻松关闭... 你有没有发现,安卓系统的辅助功能真是贴心到不行啊!不过,有时候这些功能太多,用起来有点乱糟糟的。别急...
安卓系统outlook邮件设置... 你有没有发现,自从你把手机升级到了安卓系统,邮件管理变得有点复杂呢?别急,今天就来手把手教你如何设置...
安卓系统停止向华为,自主操作系... 你知道吗?最近科技圈可是炸开了锅!安卓系统突然宣布停止向华为提供技术支持,这可不仅仅是两家公司之间的...
安卓系统连接被重置,原因解析与... 最近你的安卓手机是不是有点儿闹脾气呢?连接被重置的问题让你头疼不已?别急,今天就来给你详细解析一下这...
安卓系统自占内存,揭秘内存占用... 你有没有发现,你的安卓手机越来越慢了?是不是觉得内存不够用,打开个应用都卡得要命?别急,今天就来跟你...
安卓系统怎么转windows,... 你是不是也和我一样,手里拿着一台安卓手机,心里却想着换一台Windows系统的电脑呢?别急,今天就来...
安卓系统相册软件下载,下载与使... 手机里的相册是不是已经满满当当,想要给它们找个新家?别急,今天就来给你安利几款超好用的安卓系统相册软...
安卓9系统优化软件,解锁流畅体... 你有没有发现,自从你的安卓手机升级到了安卓9系统,运行速度好像变得更快了?是不是觉得手机变得更加流畅...
各厂商安卓系统对比,性能、特色... 你有没有发现,现在手机市场上安卓系统的竞争可是相当激烈呢!各大厂商纷纷推出自己的特色系统,让人眼花缭...
车机进入安卓系统,智能驾驶体验... 你有没有发现,最近你的车机系统好像变得不一样了?没错,车机系统正在悄悄地进入安卓的大家庭!这可不是什...
安卓系统自带壁纸高清,自带高清... 亲爱的手机控们,你是否曾为安卓系统自带的那些高清壁纸而驻足欣赏?那些色彩斑斓、风格迥异的画面,是不是...
安卓机换成钟表系统,探索智能穿... 你有没有想过,你的安卓手机其实也可以换上钟表系统呢?是的,你没听错,就是那种优雅、简洁、充满艺术感的...