如果想通过实现算法来计算3 + 12 * 2 - 2^2,第一步需要先把我们熟悉的中缀表达式转成后缀表达式。
思路
首先将输入的中缀表达式转换为一个数组,然后遍历这个数组,对于每个元素进行以下判断和操作:
- 如果它是一个左括号,就将其压入栈;
- 如果它是一个右括号,就将栈中的运算符弹出并添加到输出队列,直到遇到左括号;
- 如果它是一个数字,就直接添加到输出队列;
- 如果它是一个运算符,就将栈中优先级高于或等于它的运算符弹出并添加到输出队列,然后将它压入栈。
最后,将栈中剩余的运算符弹出并添加到输出队列。
代码实现
const operators = {
'*': {
priority: 2,
associativity: 'left'
},
'/': {
priority: 2,
associativity: 'left'
},
'+': {
priority: 1,
associativity: 'left'
},
'-': {
priority: 1,
associativity: 'left'
},
'^': {
priority: 3,
associativity: 'right'
}
}
function infixToRPN(infix) {
let resultList = []
let charList = []
infix = infix.replace(/\s+/g, '')
infix = infix.replace(/-\d+/g, match => match.replace('-', '#')) // 将负数替换为特殊标记
infix = infix.split(/([\+\*\/\^\(\)])/).filter(item => item)
infix = infix.map(item => item.replace('#', '-')) // 将特殊标记替换回负数
for ( let i = 0; i < infix.length; i++ ) {
let value = infix[i]
if (value === '(') {
// 如果它是一个左括号,就将其压入栈;
charList.push(value)
} else if (value === ')') {
// 如果它是一个右括号,就将栈中的运算符弹出并添加到输出队列,直到遇到左括号;
let char = charList.pop()
while (char !== '(' && char) {
resultList.push(char)
char = charList.pop()
}
} else if (/\d/.test(value)) {
// 如果它是一个数字,就直接添加到输出队列;
resultList.push(value)
} else {
// 如果它是一个运算符,就将栈中优先级高于或等于它的运算符弹出并添加到输出队列,然后将它压入栈。
let len = charList.length
let char = charList[len - 1]
while (len && operators[char] && operators[char].priority >= operators[value].priority && operators[char].associativity === 'left') {
resultList.push(charList.pop())
len = charList.length
char = charList[len - 1]
}
charList.push(value)
}
}
// 最后,将栈中剩余的运算符弹出并添加到输出队列。
let char = charList.pop()
while (char) {
resultList.push(char)
char = charList.pop()
}
return resultList
}
写在最后
该算法的最关键的点,理解运算符的入栈规则,需要先把优先级更高的运算符先输出,因为就符合我们日常计算的乘法的优先级高于加法的规则。
还有比较重要的点,就是计算的公式中含有负数的时候,需要先把负数的符号先替换成别的特殊字段,根据操作符剪切字符串成数组的时候才不会把负数处理成整数和-号。转成数组后,再把特殊字符替换回负号。如果你们有更好的实现方法,欢迎评论区留言,大家一起学习交流~~