Eval Expression(栈)
表达式求值是指给定一个表达式字符串,求得表达式最后的值。
例如给定表达式: 3 + 2 * (4 + 1) ,通过表达式求值后得到的值为13。
这里之所以写LeetCode是因为做LC中题目时碰到类似题目,所以把这个通用问题写一写以作记录。
解决方法(使用栈求值):
这里只介绍使用栈的版本,其他方法还有待发掘。
其实解决思路是挺明确的,大体是用两个栈分别存储操作符和操作数,然后顺序解析字符串,关键在于操作符的操作。这里分为两种情况:
- 操作符的优先级:如果遇到的操作符优先级大于栈顶操作符的优先级,则操作符入栈。
- )的问题:如果遇到反括号,则除了遵守优先级规定外,遇到(要一起消除。
循环完成后,根据栈中的内容进行计算,最后得到的数为结果。
注:这里没有考虑小数情况(包括除法)和式子中有符号的情况。
实现
虽然程序并不复杂,思路也很直接。但是在实现中需要注意不要使用某个数来代替优先级,如’‘+’的优先级是1‘ 等。下面会做详细说明:
首先来看优先级表:
Priorities[ '+' ][ '-' ] = '>' ;
Priorities[ '+' ][ '+' ] = '>' ;
Priorities[ '+' ][ '*' ] = '<' ;
Priorities[ '+' ][ '/' ] = '<' ;
Priorities[ '+' ][ '(' ] = '<' ;
Priorities[ '+' ][ ')' ] = '>' ;
Priorities[ '-' ][ '-' ] = '>' ;
Priorities[ '-' ][ '+' ] = '>' ;
Priorities[ '-' ][ '*' ] = '<' ;
Priorities[ '-' ][ '/' ] = '<' ;
Priorities[ '-' ][ '(' ] = '<' ;
Priorities[ '-' ][ ')' ] = '>' ;
Priorities[ '*' ][ '-' ] = '>' ;
Priorities[ '*' ][ '+' ] = '>' ;
Priorities[ '*' ][ '*' ] = '>' ;
Priorities[ '*' ][ '/' ] = '>' ;
Priorities[ '*' ][ '(' ] = '<' ;
Priorities[ '*' ][ ')' ] = '>' ;
Priorities[ '/' ][ '-' ] = '>' ;
Priorities[ '/' ][ '+' ] = '>' ;
Priorities[ '/' ][ '*' ] = '>' ;
Priorities[ '/' ][ '/' ] = '>' ;
Priorities[ '/' ][ '(' ] = '<' ;
Priorities[ '/' ][ ')' ] = '>' ;
Priorities[ '(' ][ '+' ] = '<' ;
Priorities[ '(' ][ '-' ] = '<' ;
Priorities[ '(' ][ '*' ] = '<' ;
Priorities[ '(' ][ '/' ] = '<' ;
Priorities[ '(' ][ '(' ] = '<' ;
Priorities[ '(' ][ ')' ] = '=' ;
可以看到这个优先级是没有传递性的,例如‘(’ 和 ‘+’,对应表中都是’<’。举个栗子:
栈顶是’(‘,遇上‘+’,应该将+入栈,则 ‘+’ > ‘(’
栈顶是’+’, 遇上‘(’,应该将(入栈,则 ‘+’ < ‘(‘
所以用数字来表示优先级是不合适的。
下面的的代码借鉴这个博客
写的比较清楚
#include <string>
#include <iostream>
#include <unordered_map>
using namespace std ;
// 运算符优先级表
unordered_map< char , unordered_map< char , char > > Priorities ;
// 初始化运算符优先级定义数据
void InitPriorities( )
{
Priorities[ '+' ][ '-' ] = '>' ;
Priorities[ '+' ][ '+' ] = '>' ;
Priorities[ '+' ][ '*' ] = '<' ;
Priorities[ '+' ][ '/' ] = '<' ;
Priorities[ '+' ][ '(' ] = '<' ;
Priorities[ '+' ][ ')' ] = '>' ;
Priorities[ '-' ][ '-' ] = '>' ;
Priorities[ '-' ][ '+' ] = '>' ;
Priorities[ '-' ][ '*' ] = '<' ;
Priorities[ '-' ][ '/' ] = '<' ;
Priorities[ '-' ][ '(' ] = '<' ;
Priorities[ '-' ][ ')' ] = '>' ;
Priorities[ '*' ][ '-' ] = '>' ;
Priorities[ '*' ][ '+' ] = '>' ;
Priorities[ '*' ][ '*' ] = '>' ;
Priorities[ '*' ][ '/' ] = '>' ;
Priorities[ '*' ][ '(' ] = '<' ;
Priorities[ '*' ][ ')' ] = '>' ;
Priorities[ '/' ][ '-' ] = '>' ;
Priorities[ '/' ][ '+' ] = '>' ;
Priorities[ '/' ][ '*' ] = '>' ;
Priorities[ '/' ][ '/' ] = '>' ;
Priorities[ '/' ][ '(' ] = '<' ;
Priorities[ '/' ][ ')' ] = '>' ;
Priorities[ '(' ][ '+' ] = '<' ;
Priorities[ '(' ][ '-' ] = '<' ;
Priorities[ '(' ][ '*' ] = '<' ;
Priorities[ '(' ][ '/' ] = '<' ;
Priorities[ '(' ][ '(' ] = '<' ;
Priorities[ '(' ][ ')' ] = '=' ;
// 不存在操作符1是)和 操作符2 比较的情况
// 因为 ) 会迫使之前的操作符进行运算。
// 直到遇到匹配的“(”操作符,双双被消除掉
// 所以下面的数据无意义。
Priorities[ ')' ][ '+' ] = '?' ;
Priorities[ ')' ][ '-' ] = '?' ;
Priorities[ ')' ][ '*' ] = '?' ;
Priorities[ ')' ][ '/' ] = '?' ;
Priorities[ ')' ][ '(' ] = '?' ;
Priorities[ ')' ][ ')' ] = '?' ;
}
// 计算2个操作数 加减乘除 的结果。
float Calculate( float Operand1 , float Operand2 , char Operator )
{
float Ret = 0 ;
if ( Operator == '+' )
{
Ret = Operand1 + Operand2 ;
}
else if ( Operator == '-' )
{
Ret = Operand1 - Operand2 ;
}
else if ( Operator == '*' )
{
Ret = Operand1 * Operand2 ;
}
else if ( Operator == '/' )
{
Ret = Operand1 / Operand2 ;
}
return Ret ;
}
// 计算 加减,不带括号的表达式
float EvaluateExpression( const string& str )
{
vector< float > Operands ; // 操作数栈,也可以用 stack< float >
vector< char > Operators ; // 操作符栈,也可以用 stack< char >
float OperandTemp = 0 ;
char LastOperator = 0 ; // 记录最后遇到的操作符
for ( size_t i = 0 , size = str.size( ) ; i < size ; ++i )
{
const char& ch = str[ i ] ;
if ( '0' <= ch && ch <= '9' )
{ // 读取一个操作数
OperandTemp = OperandTemp * 10 + ch - '0' ;
}
else if ( ch == '+' || ch == '-' || ch == '*' || ch == '/' ||
ch == '(' || ch == ')' )
{
// 有2种情况 是没有操作数需要入栈保存的。
// 1 当前操作符是 “(”。(的左边的操作符已经负责操作数入栈了。
// 2 上一次遇到的操作符是“)”。)本身会负责操作数入栈,)后面紧跟的操作符不需要再负责操作数入栈。
if ( ch != '(' && LastOperator != ')' )
{
// 遇到一个操作符后,意味着之前读取的操作数已经结束。保存操作数。
Operands.push_back( OperandTemp ) ;
// 清空,为读取下一个操作符做准备。
OperandTemp = 0 ;
}
// 当前遇到的操作符作为操作符2,将和之前遇到的操作符(作为操作符1)进行优先级比较
const char& Opt2 = ch ;
for ( ; Operators.size( ) > 0 ; )
{
// 比较当前遇到的操作符和上一次遇到的操作符的优先级
const char& Opt1 = Operators.back( ) ;
char CompareRet = Priorities[ Opt1 ][ Opt2 ] ;
if ( CompareRet == '>' )
{ // 如果操作符1 大于 操作符2 那么,操作符1应该先计算
// 取出之前保存的操作数2
float Operand2 = Operands.back( ) ;
Operands.pop_back( ) ;
// 取出之前保存的操作数1
float Operand1 = Operands.back( ) ;
Operands.pop_back( ) ;
// 取出之前保存的操作符。当前计算这个操作符,计算完成后,消除该操作符,就没必要保存了。
Operators.pop_back( ) ;
// 二元操作符计算。并把计算结果保存。
float Ret = Calculate( Operand1 , Operand2 , Opt1 ) ;
Operands.push_back( Ret ) ;
}
else if ( CompareRet == '<' )
{ // 如果操作符1 小于 操作符2,说明 操作符1 和 操作符2 当前都不能进行计算,
// 退出循环,记录操作符。
break;
}
else if ( CompareRet == '=' )
{
// 操作符相等的情况,只有操作符2是“)”,操作数1是“(”的情况,
// 弹出原先保存的操作符“(”,意味着“(”,“)”已经互相消掉,括号内容已经计算完毕
Operators.pop_back( ) ;
break;
}
} // end for
// 保存当前遇到操作符,当前操作符还缺少右操作数,要读完右操作数才能计算。
if ( Opt2 != ')' )
{
Operators.push_back( Opt2 ) ;
}
LastOperator = Opt2 ;
}
} // end for
/*
上面的 for 会一面遍历表达式一面计算,如果可以计算的话。
当遍历完成后,并不代表整个表达式计算完成了。而会有2种情况:
1.剩余1个运算符。
2.剩余2个运算符,且运算符1 小于 运算符2。这种情况,在上面的遍历过程中是不能进行计算的,所以才会被遗留下来。
到这里,已经不需要进行优先级比较了。情况1和情况2,都是循环取出最后读入的操作符进行运算。
*/
if ( LastOperator != ')' )
{
Operands.push_back( OperandTemp ) ;
}
for ( ; Operators.size( ) > 0 ; )
{
// 取出之前保存的操作数2
float Operand2 = Operands.back( ) ;
Operands.pop_back( ) ;
// 取出之前保存的操作数1
float Operand1 = Operands.back( ) ;
Operands.pop_back( ) ;
// 取出末端一个操作符
char Opt = Operators.back( ) ;
Operators.pop_back( ) ;
// 二元操作符计算。
float Ret = Calculate( Operand1 , Operand2 , Opt ) ;
Operands.push_back( Ret ) ;
}
return Operands[ 0 ] ;
}