中缀表达式转换为前缀表达式
在《前缀表达式的计算》中,我们讨论了对前缀表达式如何计算:设置一个操作数栈,对前缀表达式从右到左扫描,遇到操作数直接入栈,遇到操作符则从操作数栈弹栈,先弹left值后弹right值,根据操作符进行相应的计算,并将计算结果压入到操作数栈中,最终将整个前缀表达式扫面完毕。这时操作数栈中只有一个元素,该元素的值即为前缀表达式的值。
在《中缀表达式转换为后缀表达式》中,我们讨论了如何将一个中缀表达式转换为其对应的后缀表达式。其思想为:设置一个操作符栈,如果遇到操作数,则直接将操作数放进后缀表达式中,如果遇到非操作数,则:如果是左括号,则将左括号入栈;如果是右括号,则从操作符栈中将操作符弹栈,放入后缀表达式中,直至栈空或遇到栈中的左括号,并将左括号弹栈;如果是其他操作符,则比较其优先级与栈中操作符优先级情况,如果栈中的操作符的优先级大于等于当前操作符,则将栈中操作符弹栈,直至栈空,或栈中操作符优先级小于当前操作符的优先级,将当前操作符压栈。当从左到右顺序扫描完整个中缀表达式后,检测操作符栈,如果非空,则依次弹栈,将弹出的操作符依次压入到后缀表达式中。最终,得到中缀表达式对应的后缀表达式。如果还想计算后缀表达式的值,则可以参考《后缀表达式的计算》。
本文我们是讨论如何将中缀表达式转换为前缀表达式。
我们先给出中缀表达式转换前缀表达式的程序,然后再对程序进行相关讲解,之后在与中缀表达式转换后缀表达式的过程进行比较,分析其中的差异存在于哪里。
// 中缀表达式转换为前缀表达式 #include <iostream> #include <vector> #include <string> #include <sstream> #include <map> #include <stack> #include <algorithm> using namespace std; void GetInfix(vector<string>& infix) { infix.clear(); string line; getline(cin, line); istringstream sin(line); string tmp; while (sin >> tmp) { infix.push_back(tmp); } } // 初始化操作符 void InitOperators(map<string, int>& opers) { opers.clear(); opers["("] = 100; opers[")"] = 900; opers["+"] = 100; opers["-"] = 100; opers["*"] = 200; opers["/"] = 200; } bool IsOperator(const string& op, const map<string, int>& opers) { auto cit = opers.find(op); if (cit != opers.end()) { return true; } else { return false; } } void InfixToPrefix(const vector<string>& infix, vector<string>& prefix, map<string, int>& opers) { prefix.clear(); stack<string> stk; // 操作符栈 for (int i = infix.size() - 1; i >= 0; --i) // 从右到左扫描 { if (!IsOperator(infix[i], opers)) // 如果是操作数 { prefix.push_back(infix[i]); } else // 如果是操作符 { if (infix[i] == ")") // 如果是右括号,则直接入栈 { stk.push(infix[i]); } else if (infix[i] == "(") // 如果是左括号 { // 依次弹出栈中的操作符,直至遇到右括号 while (!stk.empty()) { if (stk.top() == ")") { stk.pop(); break; } else { prefix.push_back(stk.top()); stk.pop(); } } } else // 如果是其他操作符 { while (!stk.empty() && stk.top() != ")" && opers[stk.top()] > opers[infix[i]]) // 栈顶操作符优先级大于当前操作符优先级 { prefix.push_back(stk.top()); stk.pop(); } // 将当前操作符入栈 stk.push(infix[i]); } } } // 检测操作符栈是否为空 while (!stk.empty()) { prefix.push_back(stk.top()); stk.pop(); } // 将prefix翻转 reverse(prefix.begin(), prefix.end()); return; } void Display(const vector<string>& fix) { for (auto i = 0; i != fix.size(); ++i) { cout << fix[i] << ' '; } cout << endl; } int main() { map<string, int> opers; InitOperators(opers); while (true) { vector<string> infix, prefix; GetInfix(infix); Display(infix); InfixToPrefix(infix, prefix, opers); Display(prefix); cout << endl; } return 0; }
首先说明的是,我们的中缀表达式输入是用空白符间隔的,而没有对中缀表达式进行词法分析,对中缀表达式的词法分析可以参考《四则运算的词法分析》。
我们首先实现了中缀表达式的输入、操作符及其优先级的初始化、判断是否为操作符。然后重点在中缀表达式转换为前缀表达式的函数:InfixToPrefix。
中缀表达式转换前缀表达式的操作过程为:
首先设定一个操作符栈,从右到左顺序扫描整个中缀表达式,如果是操作数,则直接归入前缀表达式;如果是操作符,则检测器是否是右括号,如果是右括号,则直接将其入栈;如果是左括号,则将栈中的操作符依次弹栈,归入前缀表达式,直至遇到右括号,将右括号弹栈,处理结束;如果是其他操作符,则检测栈顶操作符的优先级与当前操作符的优先级关系,如果栈顶操作符优先级大于当前操作符的优先级,则弹栈,并归入前缀表达式,直至栈顶操作符优先级小于等于当前操作符优先级,这时将当前操作符压栈。
当扫描完毕整个中缀表达式后,检测操作符栈是否为空,如果不为空,则依次将栈中操作符弹栈,归入前缀表达式。最后,将前缀表达式翻转,得到中缀表达式对应的前缀表达式。
下面,我们结合中缀表达式转后缀表达式的过程,比较中缀转前缀与中缀转后缀的联系和区别。
|
中缀转前缀 |
中缀转后缀 |
栈 |
操作符栈 |
操作符栈 |
扫描顺序 |
从右到左 |
从左到右 |
遇到操作数 |
直接归入 |
直接归入 |
遇到右括号 |
直接入栈 |
将栈中操作符依次弹栈,归入,直至遇到左括号,将左括号弹栈,处理完毕 |
遇到左括号 |
将栈中操作符依次弹栈,归入,直至遇到右括号,将右括号弹栈,处理完毕 |
直接入栈 |
遇到其他操作符 |
检测栈顶操作符优先级与当前操作符优先级关系,如果栈顶大于当前,则出栈,归入,直至栈顶小于等于当前,并将当前操作符入栈 |
检测栈顶与当前优先级关系,如果栈顶大于等于当前则出栈,归入,直至栈顶小于当前,并将当前操作符入栈 |
操作符栈中的优先级 |
从栈底到栈顶操作优先级:非递减。即:栈顶可以大于或等于下面的 |
从栈底到栈顶优先级:递增。即:栈顶必须大于下面的 |
是否翻转 |
翻转 |
无需翻转 |
通过上表,我们可以看出中缀转前缀与中缀转后缀的最大区别在于两点:扫描顺序和操作符栈中操作符优先级的排列关系。
总结
以上我们主要讨论了中缀表达式转换前缀表达式的过程,并与中缀表达式转换后缀表达式进行了比较,找出其中的差异点。通过本文,将中缀表达式转换为前缀表达式,以及之前有《前缀表达式的计算》,这样我们可以通过前缀表达式,计算中缀表达式的值了。即先将中缀表达式转换为前缀表达式,然后对前缀表达式进行计算,计算结果即为中缀表达式的值。
下一步,我们将讨论如何将前缀表达式转换中缀表达式,如何将后缀表达式转换为中缀表达式,以及前缀表达式和后缀表达式之间的直接相互转换。