['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 ] ;
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
正则表达式匹配是一个经典的算法问题,主要是判断一个字符串是否能够完全匹配给定的正则表式。 在LeetCode上,也有一道关于正则表达式匹配的题目,题目编号是10。这道题目要求实现一个支持 '.' 和 '*' 的正则表达式匹配,其中 '.' 可以匹配任意单个字符,'*' 可以匹配零个或多个前面的元素。 解决这道题可以使用动态规划的思想,具体的思路如下: 1. 创建一个二维数组dp,dp[i][j]表示s的前i个字符与p的前j个字符是否匹配。 2. 初始化dp为true,表示空字符串与空正则表达式是匹配的。 3. 初始化dp[i]为false,表示非空字符串与空正则表达式是不匹配的。 4. 初始化dp[j],如果p[j-1]是"*",则dp[j]的值取决于dp[j-2]的值,表示将p[j-2]与p[j-1]去掉后的正则表达式是否匹配空字符串。 5. 对于其它的dp[i][j],分成两种情况: - 如果p[j-1]是"."或者与s[i-1]相等,则dp[i][j]的值取决于dp[i-1][j-1]的值,表示将s[i-1]和p[j-1]去掉后的字符串是否匹配。 - 如果p[j-1]是"*",则dp[i][j]的值取决于以下两种情况: - dp[i][j-2]的值,表示将p[j-2]和p[j-1]去掉后的正则表达式是否匹配s的前i个字符。 - dp[i-1][j]的值,表示将s[i-1]与p[j-2]匹配后的字符串是否匹配p的前j个字符。 6. 最后返回dp[s.length()][p.length()]的值,表示整个字符串s与正则表达式p是否完全匹配。 以上是一种使用动态规划解决正则表达式匹配问题的思路,具体的实现可以参考LeetCode官方提供的递归思路的解法。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [LeetCode算法 —— 正则表达式匹配(详解官方动态规划思想)](https://blog.csdn.net/weixin_42100963/article/details/106953141)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值