['LeetCode']表達式求值

Eval Expression(棧)

表達式求值是指給定一個表達式字符串,求得表達式最後的值。
例如給定表達式: 3 + 2 * (4 + 1) ,通過表達式求值後得到的值爲13

這裏之所以寫LeetCode是因爲做LC中題目時碰到類似題目,所以把這個通用問題寫一寫以作記錄。

解決方法(使用棧求值):

這裏只介紹使用棧的版本,其他方法還有待發掘。

其實解決思路是挺明確的,大體是用兩個棧分別存儲操作符操作數,然後順序解析字符串,關鍵在於操作符的操作。這裏分爲兩種情況:

  1. 操作符的優先級:如果遇到的操作符優先級大於棧頂操作符的優先級,則操作符入棧。
  2. 的問題:如果遇到反括號,則除了遵守優先級規定外,遇到要一起消除。

循環完成後,根據棧中的內容進行計算,最後得到的數爲結果。

注:這裏沒有考慮小數情況(包括除法)和式子中有符號的情況。

實現

雖然程序並不複雜,思路也很直接。但是在實現中需要注意不要使用某個數來代替優先級,如’‘+’的優先級是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 ] ;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章