【JS算法练习】彻底搞懂逆波兰表达式的求法,再复杂的表达式也不用慌~

前言

表达式求值是校招面试/笔试中常考的一道算法题,即可以考察求职者的算法功底,又可以考察求值者思考问题的全面性。下面我们就来看看如何使用逆波兰表达式来求解各类复杂的表达式吧~

问题描述:

以字符串的形式给出表达式,请输出该表达式的求值结果。例如:
输入:‘1+(12*3-4/2)-1’
输出:34

什么是逆波兰表达式?

逆波兰表达式又称为后缀表达式,代表的含义是操作数在前,运算符在后。
比如:1+2,用逆波兰表达式来写的话,就是12+.
而1+2这种写法称为中缀表达式,即运算符在两个操作数之间,也是我们平常最常采用的写法。

逆波兰表达式的优点

逆波兰表达式的优点是可以把复杂表达式转换为可以依靠简单的操作得到计算结果的表达式。
只用两种简单操作:入栈和出栈,就可以搞定任何普通表达式的运算。

逆波兰表达式求值步骤

  1. 将表达式字符串转换为表达式数组arr,数组的每个元素为操作数或者运算符(转换为数组的目的是防止有两位及以上的数字时在后缀表达式中无法分辨)
  2. 生成逆波兰表达式
    声明两个数组,用于生成逆波兰表达式。opeStack为运算符数组,suffixExp为原表达式数组对应的逆波兰表达式数组(JS中数组既可以用作栈,也可以用作队列)
  • 遍历表达式数组arr,遇到数字,则直接压入suffixExp数组中
  • 遍历表达式数组arr,遇到运算符,就压入opeStack中。但是运算符在压入opeStack前,需要先判断运算符栈的结构:
    • 如果运算符为空,则直接压入栈.
    • 如果运算符不为空,则需要进一步判断来决定如何处理:
      • 如果当前要入栈的运算符为’+‘或者’-’
        • 如果栈中有元素&无左括号’(',则先弹出栈中元素,然后将当前运算符压入栈。
        • 如果栈中有元素&有左括号’(‘,则弹出栈中元素直至栈顶元素为左括号’(’
      • 如果当前要入栈的运算符为’*‘或者’/’
        • 如果栈顶元素为’*‘或者’/',则先弹出栈顶元素,再入栈当前运算符
        • 如果栈顶元素为’+‘或者’-'‘或者(’,则直接入栈
      • 如果当前要入栈的运算符为左括号’(’
        直接入栈
      • 如果当前要入栈的运算符为右括号’)’
        弹出栈中元素,直至’('被弹出
  • 直至遍历完表达式数组,判断运算符栈是否为空,如果不为空,则依次弹栈运算符数组,并压入逆波兰表达式数组中
  1. 求解逆波兰表达式
    按照后缀表达式的求解方法,依次遍历逆波兰表达式数组,遇到运算符,就与运算符前的两个数组元素做运算,并将这三个元素替换为求解的结果,直至逆波兰表达式数组剩余一个元素,就是最后我们要求解的结果啦~

算法流程图如下图所示:
在这里插入图片描述

代码实现

第一步:把字符串表达式转换为数组表达式

let opeStack = [];
let suffixExp = [];
let numReg = /^[0-9]+$/;

// 把表达式的字符串转换为数组
function str2arr(str) {
    let arr = [];
    let frontNum = 0;
    for (let i = 0; i < str.length; i++) {
        if (numReg.test(str[i])) {
            frontNum = frontNum * 10 + parseInt(str[i]);
            if (i === str.length - 1) {
                arr.push(frontNum);
            }
            continue;
        }
        if (['('].indexOf(str[i]) > -1) {
            arr.push(str[i]);
            continue;
        }
        // 处理当前符号以及当前符号之前的元素
        if (str[i - 1] !== ')') {
            arr.push(frontNum);
        }
        arr.push(str[i]);
        frontNum = 0;
    }
    return arr;
}

第二步:生成逆波兰表达式

// 这里传入的strArr必须是一个数组,不然没法处理大于等于10的数
function createSuffixExp(str) {
    for (let i of str) {
        if (numReg.test(i)) {
            suffixExp.push(i);
        } else if (['*', '/'].indexOf(i) >= 0) {
            if (['*', '/'].indexOf(opeStack[opeStack.length - 1]) > -1) {// 如果运算符里有优先级相同的,就先弹栈,再压栈
                suffixExp.push(opeStack.pop());
                opeStack.push(i);
                continue;
            }
            opeStack.push(i);
        } else if ([')'].indexOf(i) >= 0) {
            // 弹栈,直到弹出来的是左括号为止
            let ele = opeStack.pop();
            while (ele !== '(') {
                suffixExp.push(ele);
                ele = opeStack.pop();
            }
        } else if (i === '(') {
            opeStack.push(i);
        } else if (opeStack.length > 0) {
            // 如果没有括号隔着,不会有*在+的下面
            while (opeStack.length > 0 && opeStack[opeStack.length - 1] !== '(') {
                suffixExp.push(opeStack.pop());
            }
            opeStack.push(i);
        } else {
            opeStack.push(i);
        }
    }
    while (opeStack.length > 0) {
        suffixExp.push(opeStack.pop());
    }
}

第三步:计算逆波兰表达式

function calcExp(str) {
    while (str.length > 1) {
        for (let i = 0; i < str.length; i++) {
            if (['+', '-', '*', '/'].indexOf(str[i]) > -1) {
                let num1 = parseInt(str[i - 2]);
                let num2 = parseInt(str[i - 1]);
                let ret = 0;
                switch (str[i]) {
                    case '+':
                        ret = num1 + num2;
                        break;
                    case '-':
                        ret = num1 - num2;
                        break;
                    case '*':
                        ret = num1 * num2;
                        break;
                    case '/':
                        ret = num1 / num2;
                        break;
                }
                str.splice(i - 2, 3, ret);
                break;
            }
        }
    }
    // 使用pop是为了置空str,从而让js的垃圾回收机制收回内存。
    return str.pop();
}

最后我们可以验证一下输出:

let str = '1+(12*3-1)';
// let str = '1+(12*3-4/2)-1';
str = str2arr(str);
// 先把中缀表达式转换为后缀表达式
createSuffixExp(str);
// 接下来是对逆波兰表达式做运算了
console.log(calcExp(suffixExp));

完整代码如下所示:

let opeStack = [];
let suffixExp = [];
let numReg = /^[0-9]+$/;

// 把表达式的字符串转换为数组
function str2arr(str) {
    let arr = [];
    let frontNum = 0;
    for (let i = 0; i < str.length; i++) {
        if (numReg.test(str[i])) {
            frontNum = frontNum * 10 + parseInt(str[i]);
            if (i === str.length - 1) {
                arr.push(frontNum);
            }
            continue;
        }
        if (['('].indexOf(str[i]) > -1) {
            arr.push(str[i]);
            continue;
        }
        // 处理当前符号以及当前符号之前的元素
        if (str[i - 1] !== ')') {
            arr.push(frontNum);
        }
        arr.push(str[i]);
        frontNum = 0;
    }
    return arr;
}

// 这里传入的strArr必须是一个数组,不然没法处理大于等于10的数
function createSuffixExp(str) {
    for (let i of str) {
        if (numReg.test(i)) {
            suffixExp.push(i);
        } else if (['*', '/'].indexOf(i) >= 0) {
            if (['*', '/'].indexOf(opeStack[opeStack.length - 1]) > -1) {// 如果运算符里有优先级相同的,就先弹栈,再压栈
                suffixExp.push(opeStack.pop());
                opeStack.push(i);
                continue;
            }
            opeStack.push(i);
        } else if ([')'].indexOf(i) >= 0) {
            // 弹栈,直到弹出来的是左括号为止
            let ele = opeStack.pop();
            while (ele !== '(') {
                suffixExp.push(ele);
                ele = opeStack.pop();
            }
        } else if (i === '(') {
            opeStack.push(i);
        } else if (opeStack.length > 0) {
            // 如果没有括号隔着,不会有*在+的下面
            while (opeStack.length > 0 && opeStack[opeStack.length - 1] !== '(') {
                suffixExp.push(opeStack.pop());
            }
            opeStack.push(i);
        } else {
            opeStack.push(i);
        }
    }
    while (opeStack.length > 0) {
        suffixExp.push(opeStack.pop());
    }
}

// 计算逆波兰表达式
function calcExp(str) {
    while (str.length > 1) {
        for (let i = 0; i < str.length; i++) {
            if (['+', '-', '*', '/'].indexOf(str[i]) > -1) {
                let num1 = parseInt(str[i - 2]);
                let num2 = parseInt(str[i - 1]);
                let ret = 0;
                switch (str[i]) {
                    case '+':
                        ret = num1 + num2;
                        break;
                    case '-':
                        ret = num1 - num2;
                        break;
                    case '*':
                        ret = num1 * num2;
                        break;
                    case '/':
                        ret = num1 / num2;
                        break;
                }
                str.splice(i - 2, 3, ret);
                break;
            }
        }
    }
    // 使用pop是为了置空str,从而让js的垃圾回收机制收回内存。
    return str.pop();
}

let str = '1+(12*3-1)';
// let str = '1+(12*3-4/2)-1';
str = str2arr(str);
// 先把中缀表达式转换为后缀表达式
createSuffixExp(str);
// 接下来是对逆波兰表达式做运算了
console.log(calcExp(suffixExp));

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Coding101

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值