C++之字符串表达式求值

关于字符串表达式求值,应该是程序猿们机试或者面试时候常见问题之一,昨天参加国内某IT的机试,压轴便为此题,今天抽空对其进行了研究。

算术表达式中最常见的表示法形式有 中缀、前缀后缀表示法。中缀表示法是书写表达式的常见方式,而前缀和后缀表示法主要用于计算机科学领域。

中缀表示法
中缀表示法是算术表达式的常规表示法。称它为 中缀表示法是因为每个操作符都位于其操作数的中间,这种表示法只适用于操作符恰好对应两个操作数的时候(在操作符是二元操作符如加、减、乘、除以及取模的情况下)。对以中缀表示法书写的表达式进行语法分析时,需要用括号和优先规则排除多义性。

Syntax: operand1 operator operand2 Example: (A+B)*C-D/(E+F)

前缀表示法
前缀表示法中,操作符写在操作数的前面。这种表示法经常用于计算机科学,特别是编译器设计方面。为纪念其发明家 ― Jan Lukasiewicz(请参阅参考资料),这种表示法也称 波兰表示法

Syntax  : operator operand1 operand2 Example : -*+ABC/D+EF

后缀表示法
在后缀表示法中,操作符位于操作数后面。后缀表示法也称 逆波兰表示法(reverse Polish notation,RPN),因其使表达式求值变得轻松,所以被普遍使用。

Syntax  : operand1 operand2 operator Example : AB+C*DEF+/-

字符串表达式求值,一般来说采用如下方式:

一.  中缀表达式到后缀表达式的转换

要把表达式从中缀表达式的形式转换成用后缀表示法表示的等价表达式,必须了解操作符的优先级和结合性。 优先级或者说操作符的强度决定求值顺序;优先级高的操作符比优先级低的操作符先求值。 如果所有操作符优先级一样,那么求值顺序就取决于它们的 结合性。操作符的结合性定义了相同优先级操作符组合的顺序(从右至左或从左至右)。

Left associativity  : A+B+C = (A+B)+C
Right associativity : A^B^C = A^(B^C)

转换过程包括用下面的算法读入中缀表达式的操作数、操作符和括号:

  1. 初始化一个空堆栈,将结果字符串变量置空。
  2. 从左到右读入中缀表达式,每次一个字符。
  3. 如果字符是操作数,将它添加到结果字符串。
  4. 如果字符是个操作符,弹出(pop)操作符,直至遇见开括号(opening parenthesis)、优先级较低的操作符或者同一优先级的右结合符号。把这个操作符压入(push)堆栈。
  5. 如果字符是个开括号,把它压入堆栈。
  6. 如果字符是个闭括号(closing parenthesis),在遇见开括号前,弹出所有操作符,然后把它们添加到结果字符串。
  7. 如果到达输入字符串的末尾,弹出所有操作符并添加到结果字符串。

二. 后缀表达式求值

对后缀表达式求值比直接对中缀表达式求值简单。在后缀表达式中,不需要括号,而且操作符的优先级也不再起作用了。您可以用如下算法对后缀表达式求值:

  1. 初始化一个空堆栈
  2. 从左到右读入后缀表达式
  3. 如果字符是一个操作数,把它压入堆栈。
  4. 如果字符是个操作符,弹出两个操作数,执行恰当操作,然后把结果压入堆栈。如果您不能够弹出两个操作数,后缀表达式的语法就不正确。
  5. 到后缀表达式末尾,从堆栈中弹出结果。若后缀表达式格式正确,那么堆栈应该为空。

好了,基本思路讨论完毕,我们开始动手写代码,此段代码假设表达式中的预算符只包括四大基本运算符+、-、*、/,为了简化代码,我们也假设表达式中的数字只包括1-9。

函数getPostfixExp用来将一个中缀表达式转换为后缀表达式(也就是逆波兰式).

string getPostfixExp(string& infix)
{
stack<char> operator_stack;
string postfix;
for (auto p : infix)
{
if (isOperator(p))
{
while (!operator_stack.empty() && isOperator(operator_stack.top()) && priority(operator_stack.top()) >= priority(p))
{
postfix.push_back(operator_stack.top());
postfix.push_back(' ');
operator_stack.pop();
}
operator_stack.push(p);
}
else if (p == '(')
{
operator_stack.push(p);
}
else if (p == ')')
{
while (operator_stack.top() != '(')
{
postfix.push_back(operator_stack.top());
postfix.push_back(' ');
operator_stack.pop();
}
operator_stack.pop();
}
else
{
postfix.push_back(p);
postfix.push_back(' ');
}
}
while (!operator_stack.empty())
{
postfix.push_back(operator_stack.top());
postfix.push_back(' ');
operator_stack.pop();
}
postfix.pop_back();
return postfix;
}

其中isOperator函数如下:

bool isOperator(char ch)
{
switch(ch)
{
case'+':
case'-':
case'*':
case'/':
return true;
default:
return false;
}
}

其中的priority函数如下:

int priority(char a) {
int temp;
if (a == '*' || a == '/')
temp = ;
else if (a == '+' || a == '-')
temp = ;
return temp;
}

得到了后缀表达式,开始我们的求值之旅吧!

int postfixCalculate(string& postfix)
{
int first, second;
stack<int> num_stack;
for (auto p : postfix)
{
switch (p)
{
//if the item is an operator (+, -, *, or /) then // pop two numbers off the stack // make a calculation: the second number
// popped-operator-first number // push the result on the stack
case '*':
getTwoNums(num_stack, first, second);
num_stack.push(first * second);
break;
case '/':
getTwoNums(num_stack, first, second);
num_stack.push(first / second);
break;
case '+':
getTwoNums(num_stack, first, second);
num_stack.push(first + second);
break;
case '-':
getTwoNums(num_stack, first, second);
num_stack.push(first - second);
break;
case ' ':
break;
// if the item is a number push it on the stack
default:
num_stack.push(p - '');
break;
}
}
int result = num_stack.top();
num_stack.pop();
return result;
}

其中getTwoNums函数如下:

void getTwoNums(stack<int>& num_stack, int& first, int& second)
{
second = num_stack.top();
num_stack.pop(); first = num_stack.top();
num_stack.pop();
}

好了,全部的代码结束了,写个main函数试试吧!

int main()
{
string infix;
cin >> infix;
string postfix = getPostfixExp(infix);
cout << postfix << endl;
cout << postfixCalculate(postfix) << endl;
system("PAUSE");
return ;
}

写在最后的话:

字符串表达式求值方法很多,本文中利用stack结合优先级的方式,解决了这个问题。其他的方法,有表达式树的方式,编译原理的书上有讲解,大家可以结合原理,自己动手实现生成表达式树的代码,然后求值就变得so easy了,当然也有与上面两者迥然不同的方式,大家举一反三,多研究!

上一篇:一篇 JPA 总结


下一篇:【力扣LeetCode】合并两个有序数组