栈-综合应用-中缀表达式转后缀表达式

综合应用

使用栈完成一个计算表达式的结果

输入:2 * 3 - 4 / 5 * 0.2 => 0.08

思路

  1. 使用index -> 2 (第一个为止),数字放入数栈,符号放入符号栈,符号栈为空,直接放入。

  1. 遇到第二个运算符,与符号栈中的符号进行运算符优先级比较。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gvhtZGbo-1601271684334)(C:\Users\how浩\AppData\Roaming\Typora\typora-user-images\image-20200925111655503.png)]

    • 第二个优先级小于等于第一个运算符,则计算绿色框中表达式的结果;

      1. 运算结果存入栈;
      2. 将第二个运算符存入栈;
    • 第二个优先级大于第一个运算符,直接进行数值运算,结果保存到数栈中;

为什么使用栈这个数据结构?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IXSde0V7-1601271684338)(C:\Users\how浩\AppData\Roaming\Typora\typora-user-images\image-20200925111845440.png)]

整个运算下来,“-”最先入“容器”,因为优先级不够的问题最后才出“容器”参与运算,刚好符号栈这个数据结构。

代码实现

function calculator(expression) {
    expression = expression.replace(/ +/g, "");

    let expressionArr = expression.match(/(\d+|[*\-+/%]+)/g);
    console.log(expressionArr);
    let numStack = new StackArray();
    let signStack = new StackArray();

    let signsValue = {"-": 1, "+": 1, "*": 2, "/": 2, "%": 1};
    let sign = "";

    for (let i = 0; i < expressionArr.length; i++) {

        // 遍历到新的符号 且 符号栈中已经有符号
        if (!signStack.isEmpty() && signsValue[expressionArr[i]]) {
            // 窥探一下栈顶的符号
            sign = signStack.peek();

            // 如果当前符号优先级小于等于栈顶符号优先级,取出两个数字和栈顶符号进行运算
            if (signsValue[expressionArr[i]] <= signsValue[sign]) {

                let right = numStack.pop();
                let left = numStack.pop();

                let result = eval(left + signStack.pop() + right);


                // 计算结果和当前符号放入栈,准备下一轮计算
                numStack.push(result);
                signStack.push(expressionArr[i]);

            } else {
                // 当前符号优先级大于栈顶符号优先级,只取出数栈顶的一个元素和运算符下一个数字进行运算
                let curSign = expressionArr[i];
                let right = expressionArr[++i];
                let left = numStack.pop();

                let result = eval(left + curSign + right);
                numStack.push(result);
            }

            // 检测是否到最后一位字符
            if (i === expressionArr.length - 1) {
                let right = numStack.pop();
                let left = numStack.pop();
                let sign = signStack.pop();

                result = eval(left + sign + right);

                return result;
            }

            // 计算完没必要再放入栈中了
            continue;
        }


        // 第一次两个栈都为空
        if (!isNaN(expressionArr[i])) { // number

            numStack.push(expressionArr[i]);

        } else if (signsValue[expressionArr[i]]) { // sign

            signStack.push(expressionArr[i]);
        }
    }

    // 运算出最后的结果
    let right = numStack.pop();
    let left = numStack.pop();
    let lastSign = signStack.pop();

    return eval(left + lastSign + right);
}

逆波兰计算器

后缀表达式的计算器求值

如: (3+4) * 5 - 6 对应的后缀表达式就是 3 4 + 5 * 6 - ,针对后缀表达式的求值步骤:

  1. 从左至右扫描,将3和4压入栈;
  2. 遇到"+"运算符,弹出3和4,进行运算,结果压入栈;
  3. 遇到5,压入栈;
  4. 遇到"*"弹出7和5,将结果压入栈;
  5. 遇到6,压入栈;
  6. 遇到“-”,弹出35和6,进行运算,结果压入栈;
  7. 结束

实现后缀表达式计算器

输入一个后缀表达式 3 4 + 5 * 6 - => 29

function calculatePostfix(expression) {
    let expressionArr = expression.match(/([\d]+|[\/*+\-])/g);

    console.log(expressionArr);
    let numStack = new StackArray();

    for (let i = 0; i < expressionArr.length; i++) {
        // number
        if (!isNaN(expressionArr[i])) {
            numStack.push(expressionArr[i]);
        } else {
            // sign
            // 遇到符号就取出两个数字进行运算
            let right = numStack.pop();
            let left = numStack.pop();
            let sign = expressionArr[i];

            numStack.push(eval(left + sign + right));
        }
    }

    return numStack.peek();
}

从代码实现中可以发现,后缀表达式相对中缀表达式计算非常方便,很符合计算机的运算方式,遇到符号就将数字取出栈运算,将结果放入栈。

中缀表达式转后缀表达式⭐️

🌰

1 + ( ( 2 + 3 ) * 4 ) - 5  =>  1 2 3 + 4 * + 5 -

考虑以下几种情况:

     top   cur
① 1  -  2  +  3 - 4
② 1  *  2  +  3
③ 1  -  2  *  3
------------------------
④ 1 - (2 * (3 + 4)) * 5

用一个栈s1保存符号,用第二个栈s2保存后缀表达式。

扫描到数字直接进入s2。

top cur 代表符号优先级

① top 与 cur 的优先级相同, 转换:1 - 2 + 3 - 4=> 1 2 - 3 + 4 -

  1. "-"先入栈s1
  2. 扫描到"+",优先级相同,"-“先出栈,进入s2,”+"进入栈s1
  3. 扫描到"-",优先级相同,"+“先出栈,进入s2,”-"进入栈s1
  4. 扫描到最后一个数字,s1中的"-"出栈,进入s2

②top > cur,转换:1 * 2 + 3 => 1 2 * 3 +

  1. "*"先入栈s1
  2. 扫描到"+","*“弹出s1,入s2,”+"入s1
  3. 扫描到最后一个数字,s1中的"+"出栈,进入s2

③ top < cur,转换:1 - 2 * 3 => 1 2 3 * -

  1. "-"先入栈s1
  2. 扫描到"*","*“的下一个数字入栈s2,”*"入栈s2
  3. "-"入栈s2

()关键思路分析:

  1. ( 2 + 3 )括号中一定会有一个运算符的优先级被提到最高
    1. 如( 2 + 3 ) 先让(入栈,依次是+,然后是)就从栈中取出符号,并且取出(
    2. 如( ( 2 + 3 ) * 4 ) ((+入栈,遇到),取出+(此时+就是被提高优先级的符号。*入栈,遇到),取出*(此时*就是被提高优先级的符号。

如图:

在这里插入图片描述

在这里插入图片描述

()中的运算符优先级提升,同时也运用了栈先进后出的概念。

④ 1 - (2 * (3 + 4)) * 5 转换:1 2 3 4 + * 5 * -

  1. "-"入栈s1
  2. "("入栈s1
  3. "*"入栈s1
  4. "("入栈s1
  5. "+"入栈s1
  6. 扫描到")",取出"+(","+"入栈s2,此时"+"运算优先级提高
  7. 扫描到")",取出"*(","*",入栈s2,"*"运算优先级提高,此时栈s1中仅剩下"-"
  8. 扫描到"*",根据③,"*“的下一个数字入栈s2,”*“入栈s2,”-"入栈s2

小结

  1. 数字:直接入栈

  2. 符号:

    1. 栈为空 or 栈顶为"(" or cur为"(",符号直接入栈
    2. cur为")",取出栈s1中的常规运算符,进入栈s2,弹出"(",不做任何操作
    3. cur为常规运算符,top为常规运算符,进行比较
      1. top >= cur,top出栈s1,进入s2,cur进入s1
      2. top < cur,cur下一个数字入栈s2,cur入栈s2,top出栈s1,入栈s2
    4. 根据②③,扫描到最后一个数字栈s1,可能为空也可能有符号,有符号就直接入栈s2
  3. 细节:最后s2符号出栈是逆序排列,因此需要颠倒过来。

代码实现

function calculatePostfix(expression) {
    expression = expression.replace(/ +/g, "");
    expressionArr = expression.match(/([\d+]|[()\-\/+*%])/g);

    let signStack = new StackArray();
    let numStack = new StackArray();
    let signsValue = {"-": 1, "+": 1, "*": 2, "/": 2, "%": 1};
    let result = [];

    for (let i = 0; i < expressionArr.length; i++) {
        // number
        if (!isNaN(expressionArr[i])) {
            numStack.push(expressionArr[i]);

        } else {//sign
            let cur = expressionArr[i];
            let top = signStack.peek();

            if (signStack.isEmpty() || top === '(' || cur === '(') {
                signStack.push(cur);

            } else if (cur === ')') {

                numStack.push(signStack.pop());
                // 弹出'('
                signStack.pop();

            } else { // 常规运算符号
                let curValue = signsValue[cur];
                let topValue = signsValue[top];
                // 将top弹出s1
                top = signStack.pop();

                if (topValue >= curValue) {
                    numStack.push(top);
                    signStack.push(cur)

                } else {
                    numStack.push(expressionArr[++i]);
                    numStack.push(cur);
                    numStack.push(top);
                }
            }
        }
    }

    if (!signStack.isEmpty()) {
        numStack.push(signStack.pop());
    }

    let j = 0;
    while(!numStack.isEmpty()) {
        result[j++] = numStack.pop();
    }
    return result.reverse().join('');
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值