表达式二叉树
一、题目表述
使用二叉树来求解表达式输入一个表达式(表达式中的数可以是多位数,可以含多层括号),利用二叉树来表示该表达式,创建表达式树,然后利用二叉树的遍历操作求表达式的值。
二、分析
2.1 解决思路
- 1、 输入为中缀表达式,直接使用中缀表达式建立二叉树较为麻烦,可将中缀表达式转换为前后缀表达式再进行建树。本次实验使用后缀表达式建树。
- 2、将中缀表达式转为后缀表达式可使用栈。
- 3、要考虑多位数、浮点数,本次实验采用每个操作符都为字符串,即将原表达式分为若干子字符串,以分离多位数、浮点数和运算符。
- 4、利用栈建立二叉树,并用递归求值。
2.2 中缀表达式转为后缀表达式
- 1、中缀表达式即我们常见数学表达式入1+3*(5-4)/6,转为后缀表达式更便于计算机识别计算。
- 2、转换过程使用一个栈,一个用于存储结果的后缀表达式数组。
- 一般过程为:
- 1)从左到右扫描每个字符
- 2)针对每个字符有以下操作:
- i)如果是
(
直接入栈(操作符栈) - ii)如果为
)
,弹栈,直到遇到’('为止,将元素放入后缀数组 - iii)
+
、-
、*
、/
循环判断若当前元素优先级低则弹栈将其放入后缀表达数组,循环结束意味着当前元素优先级高,则入栈 - iiii)数字直接入后缀数组
- iiiii)扫描完中缀表达式后,将操作符栈中的所有操作符弹出并输出到后缀表达式栈
- i)如果是
2.3 建树
- 一个用于建树的栈
- 遇到数字直接入栈,遇到运算符则弹出两个元素作为运算符的两个子结点,然后压栈
- 最后栈中只剩一个元素,即为最后的表达式树
2.4 遍历求值
- 递归求值(中序)
三、具体实现
3.1 将字符串分离
- 由于有多位数、浮点数等,单个字符操作复杂,所以将表达式转为若干子字符串
/**
* @brief 将数字和运算符分割成子字符串存入vector向量中
* @param expreesion 原表达式
* @param result 分割后的子字符串向量
*/
void ExpressionSplit(string expreesion, vector<string>& result) {
int i = 0;
while (expreesion[i] != '\0') { //循环读取表达式内容
switch (expreesion[i]) {
case '(':
case ')':
case '+':
case '-':
case '*':
case '/': { //若为运算符
string bufstrings; //临时字符串
bufstrings += expreesion[i]; //赋给临时字符串
result.push_back(bufstrings); //放入字符串向量容器中
i++;
break;
}
default: {
string bufstring;
while ((expreesion[i] >= '0' && expreesion[i] <= '9')
|| expreesion[i] == '.') { //若为数字或小数点,循环读取到不是数字或小数点为止
bufstring += expreesion[i]; //用临时字符串接收
i++;
}
result.push_back(bufstring); //放入容器
break;
}
}
}
}
3.2 中缀转后缀
- 将分离好的中缀表达式字符串容器进行后缀转换
/**
* @brief 将中缀表达式转为后缀表达式(表达式为已经进行数字运算符分离后的容器)
* @param infix 中缀表达式
* @param suffix 后缀表达式
*/
void ExpressionConversion::InfixToSuffix(vector<string> infix, vector<string>& suffix) {
for (auto& i_infix : infix) { //遍历中缀表达式
switch (i_infix[0]) { //字符串首元素即可判断是数字还是运算符
case '(': { //如果是'('直接入栈(操作符栈)
Push("(");
break;
}
case ')': { //如果为')',弹栈,知道遇到'('为止,将元素放入后缀表达式容器
string e = Pop();
while (e[0] != '(') {
suffix.push_back(e);
e = Pop();
}
break;
}
case '+':
case '-':
case '*':
case '/': {
if (IsEmpty()) { //栈为空则直接入栈
Push(i_infix);
break;
}
string e = GetTop();
while (GetPriority(i_infix[0]) <= GetPriority(e[0])) { //否则循环判断若当前元素优先级低则弹栈将其放入后缀表达式容器
suffix.push_back(Pop());
if (IsEmpty())
break;
e = GetTop();
}
Push(i_infix); //循环结束意味着当前元素优先级高,则入栈
break;
}
default: { //数字直接入后缀数组
if (i_infix[0] >= '0' && i_infix[0] <= '9') {
suffix.push_back(i_infix);
break;
}
break;
}
}
}
while (!IsEmpty()) { //扫描完中缀表达式后,将操作符栈中的所有操作符弹出并输出到后缀表达式栈
suffix.push_back(Pop());
}
}
- 在此之中判断了运算符的优先级,具体函数为
/**
* @brief 返回字符的优先级
* @param ch 需要获取优先级的字符
* @return
*/
int ExpressionConversion::GetPriority(char ch) {
switch (ch) {
case '(':
return 0;
case '+':
case '-':
return 1;
case '*':
case '/':
return 2;
default:
return -1;
}
}
3.3 建树
- 使用栈建立二叉树
/**
* @brief 解析表达式,建立表达式树
* @param expression 原表示
* @return 表达式树的根节点
*/
TreeNode* MathExpressionTree::ParseExpression(string expression) {
vector<string> inffix; //分割后子字符串容器
ExpressionSplit(expression, inffix); //将原表达式转换为数字运算符分离的子字符串
ExpressionConversion ec; //用于将中缀表达式转为后缀表达式
vector<string> suffix; //后缀表达式子字符串容器
ec.InfixToSuffix(inffix, suffix); //转换函数
Stack<TreeNode* > treestack; //用于进行表达式转为树的栈
for (auto& i_suffix : suffix) { //遍历容器
switch (i_suffix[0]) {
case '+':
case '-':
case '*':
case '/': { //判断首元素即可判断是数字还是运算符
TreeNode* bufnode = new TreeNode(i_suffix); //是运算符新建节点
bufnode->rchild = treestack.Pop();
bufnode->lchild = treestack.Pop();
treestack.Push(bufnode); //操作完后入栈
break;
}
default: {
if (i_suffix[0] >= '0' && i_suffix[0] <= '9') { //若是数字
treestack.Push(new TreeNode(i_suffix)); //直接入栈
break;
}
break;
}
}
}
return treestack.Pop();
}
3.4 遍历求值
- 即递归求值
/**
* @brief 表达式遍历求值过程
* @param node 传入根节点
* @return 计算结果
*/
double MathExpressionTree::Evaluate(TreeNode* node) {
if (node == nullptr)
return 0.0;
switch (node->data[0]) {
case '+':
return Evaluate(node->lchild) + Evaluate(node->rchild);
case '-':
return Evaluate(node->lchild) - Evaluate(node->rchild);
case '*':
return Evaluate(node->lchild) * Evaluate(node->rchild);
case '/':
return Evaluate(node->lchild) / Evaluate(node->rchild);
default:
return stod(node->data); //将字符串类型转为double类型
}
}
四、结果
- 编写测试函数
//测试函数
void MathExpressionTreeTest() {
string str[5]; //定义五个表达式
str[0] = "1+2*(3.3+4)/20";
str[1] = "1+2*(5-3)/2";
str[2] = "12/3+2.5/4-1";
str[3] = "4.8*2+0.5-3";
str[4] = "1+2-3*4/5+(6-7)*(8/9)";
cout << "有以下表达式:" << endl;
for (int i = 0; i < 5; i++) {
cout << str[i] << endl;
}
cout << endl;
MathExpressionTree met0(str[0]); //表达式转换到二叉树
MathExpressionTree met1(str[1]);
MathExpressionTree met2(str[2]);
MathExpressionTree met3(str[3]);
MathExpressionTree met4(str[4]);
cout << "解析为二叉树后:" << endl; //转换结果
cout << str[0] << "\t\t==> " << met0.ToString() << endl;
cout << str[1] << "\t\t==> " << met1.ToString() << endl;
cout << str[2] << "\t\t==> " << met2.ToString() << endl;
cout << str[3] << "\t\t==> " << met3.ToString() << endl;
cout << str[4] << "\t==> " << met4.ToString() << endl;
cout << endl;
cout << "计算结果:" << endl; //最后值
cout << str[0] << "\t\t= " << met0.GetResult() << endl;
cout << str[1] << "\t\t= " << met1.GetResult() << endl;
cout << str[2] << "\t\t= " << met2.GetResult() << endl;
cout << str[3] << "\t\t= " << met3.GetResult() << endl;
cout << str[4] << "\t= " << met4.GetResult() << endl;
}
- 在main函数中调用,运行得到结果
有以下表达式:
1+2*(3.3+4)/20
1+2*(5-3)/2
12/3+2.5/4-1
4.8*2+0.5-3
1+2-3*4/5+(6-7)*(8/9)
解析为二叉树后:
1+2*(3.3+4)/20 ==> +(1,/(*(2,+(3.3,4)),20))
1+2*(5-3)/2 ==> +(1,/(*(2,-(5,3)),2))
12/3+2.5/4-1 ==> -(+(/(12,3),/(2.5,4)),1)
4.8*2+0.5-3 ==> -(+(*(4.8,2),0.5),3)
1+2-3*4/5+(6-7)*(8/9) ==> +(-(+(1,2),/(*(3,4),5)),*(-(6,7),/(8,9)))
计算结果:
1+2*(3.3+4)/20 = 1.73
1+2*(5-3)/2 = 3
12/3+2.5/4-1 = 3.625
4.8*2+0.5-3 = 7.1
1+2-3*4/5+(6-7)*(8/9) = -0.288889
- 经验证,结果正确。