表达式树
前言
我们在学习逆波兰表达式的时候,借用栈这一数据结构来辅助计算表达式,而表达式树这一数据结构将帮我们更好地理解前中后缀表达式,以及进行三种表达式间的相互转换。
表达式树的定义
二叉树中叶子节点存放操作数,非叶子节点存放双目操作符,我们将这样一颗二叉树称为表达式树。
如上图,就是一颗表达式树,描述了表达式"2 * 3 + 9 / 3"
我们分析该二叉树发现操作数都在叶子节点上,运算符在非叶子节点上,满足我们表达式树的定义。
我们二叉树的遍历方式无非:前序、中序、后序遍历,我们用三种方法分别对该表达式树进行遍历。
前序遍历:+ * 2 3 / 9 3
中序遍历:2 * 3 + 9 / 3
后序遍历:2 3 * 9 3 / +
我们发现中序遍历得到的表达式正是我们熟悉的中缀表达式,前序后序遍历得到的分别是我们的前缀、后置表达式(关于前中后缀表达式见)前缀后缀表达式-CSDN博客,但是对于中序遍历而言,由于运算符的优先级问题会导致我们得到的中缀表达式不一定正确,所以我们前序遍历左右子树可以分别用括号括起来。
表达式树的建立(后缀表达式转表达式树)
毫无疑问的,我们又要用到栈这一数据结构来辅助构造。
我们依次读入字符,如果是操作数,我们就建立一个存储该操作数的单节点的二叉树,压入栈中,如果遇到操作符,就从栈中弹出两颗子树作为该运算符的左右子树,再将这颗新树压入栈中,如此重复,直到栈中只剩一个元素,这个元素就是我们的表达式树。
算法思路很简单,我们下面展示一下实现代码:
#ifndef EXPTREE_H
#define EXPTREE_H
int priority(const string &op)
{
if (op == "+" || op == "-")
return 1;
if (op == "*" || op == "/")
return 2;
if (op == "^")
return 3;
return 0;
}
struct Node
{
Node(const string &val) : _val(val), _left(nullptr), _right(nullptr)
{
}
string _val;
Node *_left;
Node *_right;
};
typedef Node *ExpTree;
ExpTree CreateExpTree(const vector<string> &RPolish)
{
stack<ExpTree> s;
for (auto &opt : RPolish)
if (opt.size() > 1 || (opt[0] >= '0' && opt[0] <= '9'))
{
s.push(new Node(opt));
}
else
{
ExpTree newnode = new Node(opt);
newnode->_right = s.top();
s.pop();
newnode->_left = s.top();
s.pop();
s.push(newnode);
}
ExpTree ret = s.top();
s.pop();
return ret;
}
void PrevTraverse(const ExpTree root)
{
if (!root)
return;
cout << root->_val << " ";
PrevTraverse(root->_left);
PrevTraverse(root->_right);
}
void InTraverse(const ExpTree root)
{
if (!root)
return;
bool ltag = root->_left && !(root->_left->_val[0] >= '0' && root->_left->_val[0] <= '9') && priority(root->_val) > priority(root->_left->_val);
bool rtag = root->_right && !(root->_right->_val[0] >= '0' && root->_right->_val[0] <= '9') && priority(root->_val) > priority(root->_right->_val);
if (ltag)
cout << '(';
InTraverse(root->_left);
if (ltag)
cout << ')';
cout << root->_val;
if (rtag)
cout << '(';
InTraverse(root->_right);
if (rtag)
cout << ')';
}
void PostTraverse(const ExpTree root)
{
if (!root)
return;
PostTraverse(root->_left);
PostTraverse(root->_right);
cout << root->_val << " ";
}
#endif
我们以后缀表达式"2 1 * 3 +"为例,分别测试一下前中后序遍历结果
int main(int argc, char **argv)
{
// cout << In_To_Post("(3+4)*5/100");
vector<string> RPolish{"2", "1", "+", "3", "*"};
ExpTree root = CreateExpTree(RPolish);
PrevTraverse(root);
cout << endl;
InTraverse(root);
cout << endl;
PostTraverse(root);
return 0;
}
表达式树转带括号的表达式
前面我们简单提了一下中序遍历不一定得到的就是正确的中缀表达式,所以我们需要在遍历的时候适时加上括号
对于后缀表达式"2 1 + 3 *"
无脑思路:直接给左子树加括号右子树加括号
void InTraverse(const ExpTree root)
{
if (!root)
return;
cout << '(';
InTraverse(root->_left);
cout << ')';
cout << root->_val;
cout << '(';
InTraverse(root->_right);
cout << ')';
}
((()2())+(()1()))*(()d()))
显然这种方案会有冗余括号
我们发现(2 + 1) + 3其实可以直接写成2 + 1 + 3
而(a + 1) * 3却一定要加括号,因为*的优先级高于+
所以我们中序遍历时,如果左右子树不为空且优先级低于当前结点,那么加括号
代码如下:
void InTraverse(const ExpTree root)
{
if (!root)
return;
bool ltag = root->_left && !(root->_left->_val[0] >= '0' && root->_left->_val[0] <= '9') && Prior_opt(root->_val, root->_left->_val);
bool rtag = root->_right && !(root->_right->_val[0] >= '0' && root->_right->_val[0] <= '9') && Prior_opt(root->_val, root->_right->_val);
if (ltag)
cout << '(';
InTraverse(root->_left);
if (ltag)
cout << ')';
cout << root->_val;
if (rtag)
cout << '(';
InTraverse(root->_right);
if (rtag)
cout << ')';
}