简单算术表达式二叉树的构建和求值 (数据结构)
题目要求
先用二叉树来表示一个简单算术表达式,树的每一个结点包括一个运算符或运算数。在简单算术表达式中只包含 加 减 乘 除 和一位正整数且格式正确(不包括括号),并且要按照先乘除后加减的原则构造二叉树,下图所示为 “1+2*3-4/5” 代数表达式对应的二叉树,然后由对应的二叉树计算该表达式的值。
先用二叉树来表示一个简单算术表达式,树的每一个结点包括一个运算符或运算数。
在简单算术表达式中只包含 + - * / 和一位正整数且格式正确(不包括括号),
并且要按照先乘除后加减的原则构造二叉树,下图所示为 “1+2*3-4/5” 代数表达式对应的二叉树,
然后由对应的二叉树计算该表达式的值。
思考过程
按照题目所述, 自己根据一个更复杂更全面的表达式(出现了所有的情况),画多一个图,寻找规律。
表达式的要求:
(1)要出现 + - * / 中所有的符号
(2)各优先级符号要出现不同的次数来相互搭配
如:1+2+3*4*5+6+7+8/2*9+9
我大意了,在作图的时候忘记用减号 - 了,但在这里没有影响
二叉树的特点
1. 数字只能是叶子节点, 且叶子节点只能是数字
2. + - 符号永远只能祖宗节点或者左孩子节点
3. 如果遇到 * / 则 将其作为祖宗节点来看, 其右孩子节点只能是 数字, 左孩子节点是 * / 符号或者 数字
思路分析
1. 遍历字符串,利用栈存储节点:
p = new BTNode;
p->lchild = NULL;
p->rchild = NULL;
(1) 遇到数字:
思路:
若分情况讨论情况:
1. 栈为空,即这是字符串第一个数字
2. 左右边可能为 + - * / 其中之一,
3. 而如果是乘除,则要先进行乘除运算
可见,如果对数字进行分情况处理,将会十分复杂,
不妨考虑直接将其压栈,
在遇到运算符在进行符号的优先级进行讨论。
代码操作:p->elem = ch; sta.push(p);
(2) 遇到乘除:
思路:将 * (或/) 作为父节点,
左节点为栈顶元素 num1 或者 * (或/),
右节点为字符串下一个元素num2
代码操作:新建节点 p 赋值 * (或/),
新建节点 r 赋值 字符串下一个元素num2,
弹出栈顶元素作为节点l,
然后连接:p->lchild = l, p->rchild = r;
p->elem = ch;
BTNode *r = new BTNode;
i++;
r->elem = str[i];
r->lchild = NULL;
r->rchild = NULL;
p->rchild = r;
p->lchild = sta.top();
sta.pop();
sta.push(p);
(3) 遇到加减:
思路:通过观察二叉树中,父节点的左右孩子节点的各种可能找出规律
两种情况: [1]栈内元素个数为2,
则栈顶元素必为 * 或 / ,
则将其作为当前节点右的孩子节点,
然后弾栈,剩下元素处理过程与 [2] 一致
[2]栈内元素个数为1,
则栈内元素必为 +、-、数字(首数字)其中之一,
则将其作为当前节点的左孩子节点即可,
然后弾栈,将当前节点压栈
代码操作:
p->elem = ch;
if(sta.size() == 2) {
BTNode *r = sta.top();
sta.pop();
sta.top()->rchild = r;
}
p->lchild = sta.top();
sta.pop();
sta.push(p);
2. 遍历字符串完成后,观察到有几种情况
(1)只有 + (或-)操作, 则栈内有两个元素,
栈底元素是数字,栈顶元素是运算符。
(2)只有 * 或(/)操作,则栈内只有一个运算符。
(3)既有 + (或-)操作也有 * 或(/)操作,则栈内有两个元素,
栈底元素是 * (或/)或数字,栈顶元素是运算符。
所以可以看成(2)是一种情况,(1)(3)是另一种情况
代码操作:
if (sta.size() == 2) {
BTNode *r = sta.top();
sta.pop();
sta.top()->rchild = r;
}
BT = sta.top();
代码实现
#include <iostream>
#include <string>
#include <cstring>
#include <stack>
using namespace std;
#define ElemType char
typedef struct node {
ElemType elem;
struct node *lchild;
struct node *rchild;
}BTNode;
void createBT(BTNode* &BT, string str) {
stack<BTNode*> sta;
BTNode *p;
for(int i = 0; i < str.size(); i++) {
p = new BTNode;
p->lchild = NULL;
p->rchild = NULL;
char ch = str[i];
if(ch == '*' || ch == '/') {
p->elem = ch;
BTNode *r = new BTNode;
i++;
r->elem = str[i];
r->lchild = NULL;
r->rchild = NULL;
p->rchild = r;
p->lchild = sta.top();
sta.pop();
sta.push(p);
} else if(ch == '+' || ch == '-') {
p->elem = ch;
if(sta.size() == 2) {
BTNode *r = sta.top();
sta.pop();
sta.top()->rchild = r;
}
p->lchild = sta.top();
sta.pop();
sta.push(p);
} else {
p->elem = ch;
sta.push(p);
}
p = NULL;
free(p);
}
if (sta.size() == 2) {
BTNode *r = sta.top();
sta.pop();
sta.top()->rchild = r;
}
BT = sta.top();
}
int calculate(BTNode* &BT) {
char ch = BT->elem;
if(ch == '+') return calculate(BT->lchild) + calculate(BT->rchild);
else if(ch == '-') return calculate(BT->lchild) - calculate(BT->rchild);
else if(ch == '*') return calculate(BT->lchild) * calculate(BT->rchild);
else if(ch == '/') return calculate(BT->lchild) / calculate(BT->rchild);
else return ch - '0';
}
void displayBT(BTNode* &BT) {
if(BT != NULL){
cout << BT->elem;
displayBT(BT->lchild);
displayBT(BT->rchild);
}
else{
printf("#");
}
}
void destroyBT(BTNode* &root) {
if(root != NULL) {
destroyBT(root->lchild);
destroyBT(root->rchild);
free(root);
}
}
int main() {
string str = "1+2+3*4*5+6+7+8/2*9+9";
BTNode *BT;
createBT(BT, str);
cout << "简单算术表达式二叉树为:\n";
displayBT(BT);
cout << "\n\n该表达式的运算结果为:" << calculate(BT) << "\n\n";
destroyBT(BT);
system("pause");
return 0;
}
运算结果
总结
知识点:根据算术运算符的优先级进行分类讨论,运用了栈这一数据结构。
拓展思考:上述代码先把 * (或/) 的运算进行操作,
而 + (或-) 则先存在栈里,不难发现,
可以不通过构建二叉树,利用栈来运算简单的表达式。
拓展知识:
题目所给这一类表达式叫中缀表达式,
指操作符是以中缀形式处于操作数的中间,
这一类表达式是人们常用的算术表示方法,
但通过这道题可以发现,中缀表达式不容易被计算机解析,
要使用还要经过处理(利用栈)。
而前缀表达式(波兰式,波兰数学家Jan Lukasiewicz)
与后缀表达式(逆波兰式, RPN),则更容易被计算机进行识别运算。