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 ] ;
}