中缀式转后缀式&&后缀式计算 举例破解 逆波兰算法

突然想在自己的Java作业的计算器中添加逆波兰算法,解决计算优先级的问题,写了写感觉问题蛮多的,算法学了就忘(lll¬ω¬)
在这里回忆下自己当时学习该算法的想法,尝试解释一下逆波兰算法的两个核心,中缀式转后缀式和后缀式的计算

1.中缀表达式转换为后缀表达式
  中缀表达式就是我们平时的算数表达式的写法,运算符位于参与该运算数字的中间,例如6+2*3,直接处理该类的表达式存在困难,而处理后缀表达式则相对容易很多。转换时,利用栈存储运算符,遍历中缀式,遇到。。。
  • 数字:直接输出至后缀式中
  • 高优先级运算符或左括号( :即*、/、( ,直接入栈
  • 低优先级运算符:即+和- ,弹栈直到遇到左括号或栈空,弹出的运算符输出到后缀式中,左括号不要弹出,最后此低优先级运算符入栈
  • 右括号) :弹栈直到遇到左括号,弹出的运算符输出到后缀式中,左括号也要弹出但别手滑输出到后缀式中
  • 遍历结束后,将栈中剩余的运算符依次弹出,输出到后缀式中
例如有一表达式6+2*(1+3)+8*9,开始遍历
  1. 6:输出到后缀式,此时后缀式为6
  2. 低优先级+运算符:此时栈空,入栈,此时栈内为+
  3. 2:输出到后缀式,此时后缀式为6 2
  4. 高优先级*运算符:直接入栈,此时栈内为+ *
  5. 左括号( :直接入栈,此时栈内为+ * (
  6. 1:输出到后缀式,此时后缀式为6 2 1
  7. 低优先级+运算符:开始弹栈,直接就遇到左括号( ,弹栈结束,+运算符入栈,此时栈内为+ * ( +
  8. 3:输出到后缀式,此时后缀式为6 2 1 3
  9. 右括号) :此时栈内为+ * ( +,开始弹栈,弹出+至后缀式后遇到左括号,最后栈内为+ * ,此时后缀式为6 2 1 3 +
  10. 低优先级+运算符:此时栈内为+ *,开始弹栈,依次弹出 *、+至后缀式,此时后缀式为6 2 1 3 + * +,+运算符入栈,此时栈内为+
  11. 8:输出到后缀式,此时后缀式为6 2 1 3 + * + 8
  12. 高优先级*运算符:直接入栈,此时栈内为+ *
  13. 9:输出到后缀式,此时后缀式为6 2 1 3 + * + 8 9
  14. 遍历已结束,栈内为+ *,依次弹出至后缀式,最终得到后缀式为6 2 1 3 + * + 8 9 * +
string get_suffix(string str) {//中缀转为后缀
    stack<string> st_oper;//存储运算符的栈
    int len = str.size();
    string result;
    for(int i = 0; i < len; i++) {
        if(str[i] >= '0' && str[i] <= '9') {
            int ends = i + 1;
            while((str[ends] >= '0' && str[ends] <= '9') || str[ends] == '.')//支持小数
                ends++;
            result += str.substr(i, ends - i) + " ";//数字直接输出至后式
            i = ends - 1;//注意每次循环结束的i++
        } else {
            string oper;
            oper += str[i];//运算符转为字符串,便于判断
            if(oper == "+" || oper == "-") {//低优先级运算符向前弹栈直到栈空或遇左括号
                while(!st_oper.empty() && st_oper.top() != "(") {
                    result += st_oper.top() + " ";
                    st_oper.pop();
                }
                st_oper.push(oper);//别忘了低优先级运算符最后入栈
            } else if(oper == "*" || oper == "/" || oper == "(") {//高优先级的运算符直接入栈
                st_oper.push(oper);
            } else if(oper == ")") {//开始匹配左括号
                while(st_oper.top() != "(") {
                    result += st_oper.top() + " ";//括号之间栈内的运算符输出
                    st_oper.pop();
                }
                st_oper.pop();//记得最后弹出左括号,但不要输出至后缀式
            }
        }
    }
    while(!st_oper.empty()) {//弹出栈内最后剩余运算符
        result += st_oper.top();
        if(st_oper.size() != 1) {
            result += " ";
        }
        st_oper.pop();
    }
    return result;
}
2.后缀表达式的计算
后缀式的计算依旧为遍历该后缀式,后缀式的各部分间以空格分开,此时采用栈存储数字而不是运算符,遇到。。。
  • 数字:入栈
  • 运算符:栈顶的数字num2和栈顶下一个的数字num1进行运算,注意运算顺序,若后缀式为num1 num2 /,那么运算为num1/num2而不是num2/num1。弹出这两个数字,运算结果入栈
例如上例的6 2 1 3 + * + 8 9 * +
  1. 遍历6、2、1、3:均直接入栈,此时栈内为6 2 1 3
  2. +运算符:此时栈内为6 2 1 3,弹出栈顶和栈顶下的两个数,即3和1,将1+3结果入栈,此时栈内为6 2 4
  3. *运算符:此时栈内为6 2 4,同上,将2*4结果入栈,此时栈内为6 8
  4. +运算符:此时栈内为6 8,同上,将6+8结果入栈,此时栈内为14
  5. 遍历8、9:均直接入栈,此时栈内为14 8 9
  6. *运算符:此时栈内为14 8 9,同上,将8*9结果入栈,此时栈内为14 72
  7. +运算符:此时栈内为14 72,同上,将14+72结果入栈,此时栈内为86
  8. 遍历结束,栈内只剩下一个元素86,即为最终结果

一定要注意运算顺序,例如1+5/2,后缀式为1 5 2 / +,第一步是5/2而不是2/5

double get_result(string str) {//计算后缀表达式获得结果
    stack<double> st_num;//存储数字的栈
    int len = str.size();
    double num1, num2;
    for(int i = 0; i < len; i++) {
        int ends = i + 1;
        while(ends < len && str[ends] != ' ')//我的后缀式中以空格区分各部分,注意下标越界问题,其实C++字符串越界几个不会有大问题
            ends++;
        string part = str.substr(i, ends - i);//获取该部分
        i = ends;//每次循环结束有i++,但空格不遍历
        switch(part[0]) { //注意两数的计算顺序,重要事情说3遍
        case '+':
            num2 = st_num.top();
            st_num.pop();
            num1 = st_num.top();
            st_num.pop();
            st_num.push(num1 + num2);
            break;
        case '-':
            num2 = st_num.top();
            st_num.pop();
            num1 = st_num.top();
            st_num.pop();
            st_num.push(num1 - num2);
            break;
        case '*':
            num2 = st_num.top();
            st_num.pop();
            num1 = st_num.top();
            st_num.pop();
            st_num.push(num1 * num2);
            break;
        case '/':
            num2 = st_num.top();
            st_num.pop();
            num1 = st_num.top();
            st_num.pop();
            st_num.push(num1 / num2);
            break;
        default:
            st_num.push(stod(part));//普通数字直接入栈
        }
    }
    return st_num.top();//栈内只剩一个元素,栈顶即结果
}

逆波兰计算器完整代码如下

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <stack>

using namespace std;

string get_suffix(string str) {//中缀转为后缀
    stack<string> st_oper;//存储运算符的栈
    int len = str.size();
    string result;
    for(int i = 0; i < len; i++) {
        if(str[i] >= '0' && str[i] <= '9') {
            int ends = i + 1;
            while((str[ends] >= '0' && str[ends] <= '9') || str[ends] == '.')
                ends++;
            result += str.substr(i, ends - i) + " ";//数字直接输出
            i = ends - 1;
        } else {
            string oper;
            oper += str[i];
            if(oper == "+" || oper == "-") {//低优先级运算符向前弹栈直到栈空或遇左括号
                while(!st_oper.empty() && st_oper.top() != "(") {
                    result += st_oper.top() + " ";
                    st_oper.pop();
                }
                st_oper.push(oper);//别忘了低优先级运算符最后入栈
            } else if(oper == "*" || oper == "/" || oper == "(") {//高优先级的运算符直接入栈
                st_oper.push(oper);
            } else if(oper == ")") {//开始匹配左括号
                while(st_oper.top() != "(") {
                    result += st_oper.top() + " ";//括号之间栈内的运算符输出
                    st_oper.pop();
                }
                st_oper.pop();//记得弹出左括号
            }
        }
    }
    while(!st_oper.empty()) {
        result += st_oper.top();
        if(st_oper.size() != 1) {
            result += " ";
        }
        st_oper.pop();
    }
    return result;
}

double get_result(string str) {//计算后缀表达式获得结果
    stack<double> st_num;
    int len = str.size();
    double num1, num2;
    for(int i = 0; i < len; i++) {
        int ends = i + 1;
        while(ends < len && str[ends] != ' ')//我的后缀式中以空格区分各部分,注意下标越界问题,其实C++字符串越界几个通常不会有大问题
            ends++;
        string part = str.substr(i, ends - i);//获取该部分
        i = ends;
        switch(part[0]) { //注意两数的计算顺序,重要事情说三遍
        case '+':
            num2 = st_num.top();
            st_num.pop();
            num1 = st_num.top();
            st_num.pop();
            st_num.push(num1 + num2);
            break;
        case '-':
            num2 = st_num.top();
            st_num.pop();
            num1 = st_num.top();
            st_num.pop();
            st_num.push(num1 - num2);
            break;
        case '*':
            num2 = st_num.top();
            st_num.pop();
            num1 = st_num.top();
            st_num.pop();
            st_num.push(num1 * num2);
            break;
        case '/':
            num2 = st_num.top();
            st_num.pop();
            num1 = st_num.top();
            st_num.pop();
            st_num.push(num1 / num2);
            break;
        default:
            st_num.push(stod(part));//普通数字直接入栈
        }
    }
    return st_num.top();//栈内只剩一个元素,栈顶即结果
}

int main() {
    string str;
    cout << "输入中缀表达式:";
    cin >> str;
    str = get_suffix(str);
    cout << "后缀表达式为:" << str << endl;
    cout << "结果为:" << get_result(str) << endl;
    return 0;
}
最后来几张测试图吧,纪念我曾经为该算法逝去的一下午,以上分析权且当作参考,出问题我不负责(¬︿̫̿¬☆)

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值