news 2026/4/25 12:58:20

二叉树和表达式树的实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
二叉树和表达式树的实现

二叉树的介绍

二叉树是树这种数据结果的一种特殊情况,其每个节点的子节点树不能超过两个,二叉树差不多就是树中最常用的特殊结构了。

二叉树的分类

满二叉树

国外定义:由度为0和2的结点构成的树,没有度为1的节点。

国内定义:每一层都是满结点,高度为k的二叉树的结点固定为

完全二叉树

按层序从上到下,从左到右编号,空缺只能出现在最后一层的最右侧,前面所有层必须满。

完美二叉树

国外定义:每一层都是满结点,高度为k的二叉树的结点固定为

国内定义:每一层都是满结点,高度为k的二叉树的结点固定为

扩充二叉树

在原二叉树所有空子树的位置添加上空结点。

二叉树的性质

①二叉树的第k层最多有个节点。

②深度为d的二叉树最多有个节点

③设一棵树中,度为i的节点数为

则总节点数N==

可以推出

解释:主要是解释N=,由于一个度为2的结点会挂两个结点,一个度为1的结点会挂1个节点,一个度为0的叶子节点不挂节点,所以按道理就是树的总结点数,但是根节点没有其它节点挂着它,所以上述式子没有算上根节点,如果算上根节点的话就加1就行了。

二叉树的实现

二叉树的数据结构

typedef int ElementType; struct TreeNode; typedef struct TreeNode *PtrToNode; typedef PtrToNode Tree; struct TreeNode { ElementType Element; Tree Left; Tree Right; }; Tree CreateTree(ElementType rootElement); PtrToNode CreateNode(ElementType element); void InsertLeftChild(PtrToNode parent, ElementType element); void InsertRightChild(PtrToNode parent, ElementType element); void PreOrderTraversal(Tree T); void InOrderTraversal(Tree T); void PostOrderTraversal(Tree T); void DestroyTree(Tree T);

这里依然把二叉树的所有节点都看成是一个个指向struct TreeNode对象的指向,每个节点包含存储的数据,以及这个节点指向其左右两个子节点的指针。

树和节点的创建及销毁

Tree CreateTree(ElementType rootElement) { Tree T = (Tree)malloc(sizeof(struct TreeNode)); if (T == NULL) { printf("Failed to allocate memory for the new Tree\n"); return NULL; } T->Element = rootElement; T->Left = NULL; T->Right = NULL; return T; } PtrToNode CreateNode(ElementType element) { PtrToNode node = (PtrToNode)malloc(sizeof(struct TreeNode)); if (node == NULL) { printf("Failed to allocate memory for the new Tree\n"); return NULL; } node->Element = element; node->Left = NULL; node->Right = NULL; return node; } void DestroyTree(Tree T) { if (T != NULL) { DestroyTree(T->Left); DestroyTree(T->Right); free(T); } }

创建一个二叉树对象其实就是创建一个根节点,如果想要实现节点与节点之间的联系,就需要用到左右子节点插入的函数。

左右子节点的插入

void InsertLeftChild(PtrToNode parent,ElementType element) { if (parent == NULL) { printf("Parent is NULL\n"); return; } if (parent->Left != NULL) { printf("The LeftChild already exists"); return; } parent->Left = CreateNode(element); } void InsertRightChild(PtrToNode parent,ElementType element) { if (parent == NULL) { printf("Parent is NULL\n"); return; } if (parent->Right != NULL) { printf("The RightChild already exists"); } parent->Right = CreateNode(element); }

对于左子节点插入的函数,其逻辑就是先创建一个数据为element的节点,然后让parent这个父节点的Left指针指向这个节点。右子节点的插入也是同理。

前中后序遍历

void PreOrderTraversal(Tree T) { if (T != NULL) { printf("%d", T->Element); PreOrderTraversal(T->Left); PreOrderTraversal(T->Right); } } void InOrderTraversal(Tree T) { if (T != NULL) { InOrderTraversal(T->Left); printf("%d", T->Element); InOrderTraversal(T->Right); } } void PostOrderTraversal(Tree T) { if (T != NULL) { PostOrderTraversal(T->Left); PostOrderTraversal(T->Right); printf("%d", T->Element); } }

得益于树的结构,其前中后序的遍历只需要简单的递归就能实现,以前序遍历(根左右)为例,先打印输出根节点的值,然后再遍历根节点的左边,左边也是一棵树,这样根节点的左子节点就相当于这棵树的头节点,根据根左右的顺序就遍历这个头节点,然后再以它的左节点开始,把它的左节点看成头节点,周而复始地进行根左右这个顺序的遍历。其他情况同理。

实例

int main() { Tree T = CreateTree(1); InsertLeftChild(T, 2); InsertRightChild(T, 3); InsertLeftChild(T->Left, 4); InsertRightChild(T->Left, 5); PreOrderTraversal(T); printf("\n"); InOrderTraversal(T); printf("\n"); PostOrderTraversal(T); DestroyTree(T); return 0; }

结果如下:

表达式树的介绍

表达式树指的是其树上的节点存储的都是表达式的元素,由于加减乘除这样的运算都是对两个值进行的运算,而二叉树的每个节点的子节点又可以恰好为两个,所有理所应当地就可以用二叉树来存储一个由加减乘除构成的表达式了。以下算法可以实现后缀表达式的输入,而进行中缀表达式及其式子运算后的值的输出。程序由豆包生成。

表达式树的实现

表达式树的结构

typedef union { char op; // 操作符 int num; // 操作数(这里用整数举例) } ElementType; struct TreeNode; typedef struct TreeNode *PtrToNode; typedef PtrToNode Tree; struct TreeNode { ElementType data; int isOperator; // 标记是操作符(1)还是操作数(0) Tree Left; Tree Right; };

树的节点有四个变量,首先是存储的值,这个值可以是一个数字,也可以是一个运算符,所有其数据类型为union;其次还需要一个用于标记这个节点存储的是数字还有运算符的记号;最后就是指向左右节点的指针。

栈的辅助结构

// 栈的辅助结构(用于存储树的指针) typedef struct StackNode { Tree tree; struct StackNode *next; } StackNode, *Stack; // 创建空栈 Stack CreateStack() { Stack s = (Stack)malloc(sizeof(StackNode)); s->next = NULL; return s; } // 判断栈空 int IsStackEmpty(Stack s) { return s->next == NULL; } // 压栈 void Push(Stack s, Tree t) { StackNode *newNode = (StackNode *)malloc(sizeof(StackNode)); newNode->tree = t; newNode->next = s->next; s->next = newNode; } // 弹栈 Tree Pop(Stack s) { if (IsStackEmpty(s)) { printf("栈空,无法弹栈!\n"); exit(1); } StackNode *top = s->next; Tree t = top->tree; s->next = top->next; free(top); return t; }

由于算法涉及到需要把一个后缀表达式拆解再将每个部分放到二叉树中,所以这里用栈这种数据结构来方便以上步骤的进行。

后缀表达式转表达式树

// 创建操作数节点 Tree CreateNumNode(int num) { Tree t = (Tree)malloc(sizeof(struct TreeNode)); t->data.num = num; t->isOperator = 0; t->Left = NULL; t->Right = NULL; return t; } // 创建操作符节点 Tree CreateOpNode(char op, Tree left, Tree right) { Tree t = (Tree)malloc(sizeof(struct TreeNode)); t->data.op = op; t->isOperator = 1; t->Left = left; t->Right = right; return t; } // 后缀表达式转表达式树 Tree BuildExpressionTree(char *postfix) { Stack s = CreateStack(); int len = strlen(postfix); for (int i = 0; i < len; i++) { char c = postfix[i]; if (c == ' ') continue; // 跳过空格 if (c >= '0' && c <= '9') { // 处理多位数(简单处理:假设是单个数字) int num = c - '0'; Tree numTree = CreateNumNode(num); Push(s, numTree); } else if (c == '+' || c == '-' || c == '*' || c == '/') { // 操作符:弹出右子树、左子树 Tree right = Pop(s); Tree left = Pop(s); Tree opTree = CreateOpNode(c, left, right); Push(s, opTree); } } Tree exprTree = Pop(s); free(s); // 释放栈 return exprTree; }

需要说明的是为了简便这个算法不能识别多位数字。首先开始对后缀表达式遍历,如果遍历到的是数字,那就存入栈中,如果遍历到操作符,先把这个操作符封装成一个节点,然后从栈中弹出两个节点,让这个操作符节点的左右子节点分别为这两个节点,最后再将这个操作符节点压栈,以便之后成为别的操作符节点的子节点。最后整个栈只会有一个节点,这个节点其实就是最终的表达式树,把它弹出来后,栈就是空栈了,此时就可以释放栈的空间了。

中缀表达式的输出

// 中序遍历(输出中缀表达式,自动加括号) void InOrderExpr(Tree t) { if (t == NULL) return; if (t->isOperator) printf("("); InOrderExpr(t->Left); if (t->isOperator) { printf("%c", t->data.op); } else { printf("%d", t->data.num); } InOrderExpr(t->Right); if (t->isOperator) printf(")"); }

当得到表达式二叉树后,对它进行后序遍历得到的就是后序表达式,对它进行中序遍历得到的就是中序表达式。

表达式树的求值

// 表达式树求值 int EvaluateExprTree(Tree t) { if (t == NULL) return 0; if (!t->isOperator) { return t->data.num; // 操作数直接返回 } int leftVal = EvaluateExprTree(t->Left); int rightVal = EvaluateExprTree(t->Right); switch (t->data.op) { case '+': return leftVal + rightVal; case '-': return leftVal - rightVal; case '*': return leftVal * rightVal; case '/': return leftVal / rightVal; default: printf("未知操作符!\n"); exit(1); } }

这里通过后序遍历得到表达式树的值。其表达式树长这样:

实例

int main() { // 后缀表达式:"3 4 + 5 *" → 对应中缀 (3+4)*5 char postfix[] = "3 4 + 5 *"; Tree exprTree = BuildExpressionTree(postfix); printf("中缀表达式:"); InOrderExpr(exprTree); printf("\n"); int result = EvaluateExprTree(exprTree); printf("表达式结果:%d\n", result); // (可选)销毁树 // DestroyTree(exprTree); return 0; }

结果如下:

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/25 12:57:27

音乐解析终极指南:免费获取四大平台歌曲播放地址的完整教程

音乐解析终极指南&#xff1a;免费获取四大平台歌曲播放地址的完整教程 【免费下载链接】music-api Music API 项目地址: https://gitcode.com/gh_mirrors/mu/music-api 想要免费获取网易云音乐、QQ音乐、酷狗音乐、酷我音乐等主流平台的歌曲播放地址吗&#xff1f;musi…

作者头像 李华
网站建设 2026/4/25 12:55:17

2026届学术党必备的十大AI写作助手推荐

Ai论文网站排名&#xff08;开题报告、文献综述、降aigc率、降重综合对比&#xff09; TOP1. 千笔AI TOP2. aipasspaper TOP3. 清北论文 TOP4. 豆包 TOP5. kimi TOP6. deepseek 在诸多形形色色的AI论文写作工具里头&#xff0c;有几款是比较出众的&#xff1a;GPT凭借着强…

作者头像 李华
网站建设 2026/4/25 12:55:10

多线程代码案例2-阻塞队列

文章目录阻塞队列特点生产者消费者模型生产者消费者模型的意义多线程环境使用阻塞队列自己实现一个简单的阻塞队列1. 先创建一个基本的阻塞队列类型2. 考虑线程安全问题3. wait 和 notify 实现阻塞4.if 改为while5.最终阻塞队列6. 测试类阻塞队列特点 阻塞队列是线程安全的阻塞…

作者头像 李华
网站建设 2026/4/25 12:55:09

s2-pro音色复用实战:从客户录音中提取声纹用于营销外呼系统

s2-pro音色复用实战&#xff1a;从客户录音中提取声纹用于营销外呼系统 1. 场景需求分析 在电话营销领域&#xff0c;使用真实客户的声音进行外呼可以显著提升接通率和转化率。传统方式需要客户录制大量语音样本&#xff0c;不仅效率低下&#xff0c;而且难以保证音质一致性。…

作者头像 李华
网站建设 2026/4/25 12:55:04

ARM SME2指令集:矩阵运算与AI加速技术解析

1. ARM SME2指令集概述在移动计算和边缘AI领域&#xff0c;性能与能效的平衡一直是芯片设计的核心挑战。ARMv9架构引入的SME2&#xff08;Scalable Matrix Extension 2&#xff09;扩展&#xff0c;正是针对这一挑战的解决方案。作为SVE2&#xff08;Scalable Vector Extension…

作者头像 李华