【算法】波兰式PN和逆波兰式RPN

波兰式和逆波兰式

波兰式逆波兰式中缀表达式
定义在通常的表达式中,二元运算符总是置于与之相关的两个运算对象之前,所以,这种表示法也称为前缀表达式。将运算对象写在前面,而把运算符号写在后面。用这种表示法表示的表达式也称做后缀式。逆波兰式的特点在于运算对象顺序不变,运算符号位置反映运算顺序。一个通用的算术或逻辑公式表示方法, 操作符是以中缀形式处于操作数的中间。不容易被电脑解析,
示例3*(2-(5+1)) --> * 3 - 2 + 5 13*(2-(5+1)) --> 3 2 5 1 + - *3*(2-(5+1))
阅读方式从左至右读入表达式,如果一个操作符后面跟着两个操作数时,则计算,然后将结果作为操作数替换这个操作符和两个操作数,重复此步骤,直至所有操作符处理完毕。从左往右读入表达式,当读到第一个操作符时,从左边取出两个操作数做计算,然后将这个结果作为操作数替换这个操作符和两个操作数,重复此步骤,直至所有操作符处理完毕。此处不赘述
计算示例* 3 - 2 + 5 1
→ * 3 - 2 6
→ * 3 (-4)
→ * -12
3 2 5 1 + - *
→3 2 6 - *
→3 (-4) *
→ -12
计算过程中必须用括号将操作符和对应的操作数括起来,用于指示运算的次序。

波兰式表示法(PN):中缀表达式转换成前缀表达式

PN思路

  1. 初始化两个栈:运算符栈 S1; 操作数栈 S2
  2. 从右至左扫描中缀表达式
  3. 遇到操作数时,将其压入 S2
  4. 遇到运算符时,比较其与 S1 栈顶运算符的优先级
    1. 如果 S1 为空,或栈顶运算符为右括号 ")",或其优先级比栈顶运算符的优先级较高或相等,则直接将此运算符入栈
    2. 否则,将 S1 栈顶的运算符弹出并压入到 S2 中,再次进行与 S1 栈顶运算符的优先级比较
  5. 遇到括号
    1. 如果是右括号 ")",则直接压入 S1
    2. 如果是左括号 "(",则依次弹出 S1 栈顶的运算符,并压入 S2,直到遇到")" 为止,此时将这一对括号丢弃
  6. 重复步骤 2 至 5,直到表达式的最左边
  7. S1 剩余的运算符依次弹出并压入 S2
  8. 依次弹出 S2 中的元素并输出,结果即为中缀表达式对应的前缀表达式

PN实现

// 中缀表达式转化为前缀表达式
function PN(str) {
  const  len = str.length
  let charStack = [], pnStr =  []
  const op = {
    '+' : 1,
    '-' : 1,
    '*' : 2,
    '/' : 2,
    '(' : 3,
    ')' : 3
  }
  for (let n = len -1; n > -1 ; n--){
    const byte = str[n]
    // 数字
    if(/\d/.test(byte)) {
      pnStr.unshift(byte)
    } else if(/\(|\)/.test(byte)) {
      // 左括号出栈
      if(byte === '(') {
        let nowChar = charStack.pop()
        while(nowChar && nowChar !== ')') {
          pnStr.unshift(nowChar)
          nowChar = charStack.pop()
        }
      // 右括号入栈
      } else {
        charStack.push(byte)
      }
    // 符号
    } else {
      // 字符栈顶元素
      let nowChar = charStack[charStack.length-1]
      while(nowChar && op[byte] < op[nowChar] && nowChar !== ')') {
        charStack.pop()
        pnStr.unshift(nowChar)
        nowChar = charStack[charStack.length-1]
      }
      charStack.push(byte)
    }
 }
  while(charStack.length) {
    pnStr.unshift(charStack.pop())
  }
  return pnStr.join('')
}
const stack = ['(','1','+', '2',')' ,'*', '3','-','(', '3','+', '5',')', '/', '7']
console.log(PN(stack))  // -*+123/+357

计算前缀表达式:

function calPN(arr) {
  const op = ['+', '-', '*', '/']
  const byte = []
  while(arr.length) {
    let now = arr.pop()
    const index = op.indexOf(now)
    // 是符号
    if(index !== -1) {
      // 栈顶两元素
      const first = byte.pop(),
            end = byte.pop();
      switch(index){
        case 0:
          byte.push( first + end)
          break;
        case 1:
          byte.push( first - end)
          break;
        case 2:
          byte.push( first * end)
          break;
        case 3:
          byte.push( first / end)
          break;
      }
    // 不是符号
    } else {
      byte.push(now)
    }
 }
  return byte
}
const stack = ['-','*','+', 1, 2,3,'/','+',3,5,7,]
calPN(stack)

逆波兰表示法(RPN):中缀表达式转后缀表达式

RPN思路

  1. 初始化两个栈:运算符栈 S1; 操作数栈 S2
  2. 从左至右扫描中缀表达式
  3. 遇到操作数时,将其压入 S2
  4. 遇到运算符时,比较其与 S1 栈顶运算符的优先级
    1. 如果 S1 为空,或栈顶运算符为"(",或其优先级比栈顶运算符的优先级较高,则直接将此运算符入栈
    2. 否则,将 S1 栈顶的运算符弹出并压入到 S2 中,再次进行与 S1 栈顶运算符的优先级比较
  5. 遇到括号时
    1. 如果是"(",则直接压入 S1
    2. 如果是")",则依次弹出 S1 栈顶的运算符,并压入 S2,直到遇到"(" 为止,此时将这一对括号丢弃
  6. 重复步骤 2 至 5,直到表达式的最右边
  7. S1 剩余的运算符依次弹出并压入 S2
  8. 拼接 S2 中的元素并输出,结果即为中缀表达式对应的后缀表达式

RPN实现

// 中缀表达式转化为后缀表达式
function RPN(str) {
  let len = str.length, charStack = [], rpnStr =  []
  const op = {
    '+' : 1,
    '-' : 1,
    '*' : 2,
    '/' : 2,
    '(' : 3,
    ')' : 3
  }
  for( let n = 0; n < len; n++) {
    const byte = str[n]
    // 数字
    if(/\d/.test(byte)) {
      rpnStr.push(byte)
    } else if(/\(|\)/.test(byte)) {
      // 左括号入栈
      if(byte === '(') {
        charStack.push(byte)
      // 右括号出栈
      } else {
        let nowChar = charStack.pop()
        while(nowChar && nowChar !== '(') {
          rpnStr.push(nowChar)
          nowChar = charStack.pop()
        }
      }
    // 符号
    } else {
      // 字符栈顶元素
      let nowChar = charStack[charStack.length - 1]
      while(nowChar && op[byte] < op[nowChar] && nowChar !== '(') {
        charStack.pop()
        rpnStr.push(nowChar)
        nowChar = charStack[charStack.length - 1]
      }
      charStack.push(byte)
    }
  }
  while(charStack.length) {
    rpnStr.push(charStack.pop())
  }
  return rpnStr.join('')
}

const stack = ['(','1','+', '2',')' ,'*', '3','-','(', '3','+', '5',')', '/', '7']
console.log(RPN(stack))  // 12+3*35+7/-

计算后缀表达式

// 计算后缀表达式
function calRPN(arr) {
  const op = ['+', '-', '*', '/']
  const byte = []
  while(arr.length) {
    let now = arr.shift()
    const index = op.indexOf(now)
    // 是符号
    if(index !== -1) {
      // 栈顶两元素
      const end = byte.pop(),
            first = byte.pop();
      switch(index){
        case 0:
          byte.push( first + end)
          break;
        case 1:
          byte.push( first - end)
          break;
        case 2:
          byte.push( first * end)
          break;
        case 3:
          byte.push( first / end)
          break;
      }
    // 不是符号
    } else {
      byte.push(now)
    }
  }
  return byte
}
  
const stack = [1, 2,'+',3,'*',3,5,'+',7,'/','-']
calRPN(stack)

参考:
https://blog.csdn.net/weixin_42614080/article/details/106747483
https://zhuanlan.zhihu.com/p/143113551

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值