题解 P1175 【表达式的转换】

· · 题解

这道题的解题思路很简单:

  1. 把中缀表达式转换为后缀表达式(使用栈)
  2. 根据后缀表达式计算结果(使用栈)

这两个过程都可以用栈来实现,所以实现过程就很清晰了。

实现过程:

1. 读入中缀表达式:

因为输入的字符串长度不定,这里用string计算相对方便一些。

string s;
cin >> s;

2. 转换后缀表达式:

转换后缀表达式主要使用来实现,只需要枚举每一位,分情况处理即可。

一共有四种情况,如下:

最后若计算完成但栈中还有元素,弹出栈中所有元素并输出。

输出的字符串即为转换完成的后缀表达式。

/* 返回每个运算符对应的优先级 */
inline int priority(const char& ch) {
    switch(ch) {
    // 加减的优先级最低,我们设为1
    case '+':
    case '-':
        return 1;
    // 乘除的优先级其次,设为2
    case '*':
    case '/':
        return 2;
    // 注意这里有乘方运算,而样例里没有,最开始我就栽在这里了QwQ
    // 乘方的优先级最高,设为3
    case '^':
        return 3;
    // 括号优先级另行处理,这里可以不写
    case '(':
    case ')':
        return 0;
    // 防止意外情况导致RE,default可写可不写
    default:
        return -1;
    }
}

/* 将中缀表达式转换为后缀表达式 */
inline string toSuffix(const string& s) {
    string ret = "";    // 用于返回的后缀表达式
    stack<char> st;     // 用于实现转换的栈
    for (register int i = 0; i < s.size(); i ++) {  // 枚举每一位
        if (isdigit(s[i])) {                // 如果为数字,直接输出到结果中
            ret += s[i];
        } else if (s[i] == '(') {           // 如果为左括号,直接加入栈中
            st.push(s[i]);
        } else if (s[i] == ')') {           // 如果为右括号,
            while (st.top() != '(')         // 一直弹出并输出直到遇到左括号,
                ret += st.top(), st.pop();  // 并弹出左括号
            st.pop();
        } else {                            // 否则即为普通的运算符,
            while (!st.empty() && priority(st.top()) >= priority(s[i]))
                ret += st.top(), st.pop();  // 一直弹出并输出直到遇到比当前优先级低的运算符,
            st.push(s[i]);                  // 最后把当前运算符压入栈中
        }
    }
    while (!st.empty())             // 最后如果栈中还有剩余的字符,直接弹出并输出
        ret += st.top(), st.pop();
    return ret;                     // 最后返回结果
}

3. 计算后缀表达式的值,并输出计算过程:

计算后缀表达式也使用栈来实现,计算过程同理要分情况处理。

一共分为两种情况,如下:

输出计算过程的方法为:

  1. 先输出已计算完成的数,即所有的栈中元素(需要按照从栈底到栈顶的顺序)。
  2. 再输出还未计算的数,即字符串中当前字符到末尾的字符串。

因为需要输出栈中元素,为了方便,不需要将每个元素弹出后输出,并且使每次压入和弹出的操作的时间复杂度降到尽可能的小,这里没有使用stack(每次需弹出)和vector(常数相对较大)而使用了list(链表)(也可以说我有点强迫症吧2333)

/* 根据运算符计算相应的运算结果 */
inline int calcNum(const int& a, const int& b, const int& symbol) {
    switch(symbol) {
    case '+':
        return a + b;
    case '-':
        return a - b;
    case '*':
        return a * b;
    case '/':
        return a / b;
    case '^':
        return (int)pow(a, b);
    default:    // 与上面同理,default可写可不写
        return -1;
    }
}

/* 将后缀表达式加上空格后输出 */
inline void printSuffix(const string& s) {
    for (register int i = 0; i < s.size(); i ++)
        cout << s[i] << ' ';
    cout << endl;
}

/* 计算后缀表达式并输出计算过程 */
inline void calc(const string& s) {
    /**
     * 实现计算的数字栈,为了输出方便(不需要将每个元素弹出)
     * 并且为了使压入和弹出操作的时间复杂度降到O(1),
     * 这里没有使用vector而是使用了list(链表)
     */
    list<int> st;
    printSuffix(s);     // 注意要先输出后缀表达式,我之前栽在这里了QwQ
    for (register int i = 0; i < s.size(); i ++) {
        if (isdigit(s[i])) {    // 如果遇到数字直接压入栈
            st.push_back(s[i] - '0');
        } else {                // 否则计算结果并压入栈
            register int a, b;  //取出栈顶的两个元素
            a = st.back();
            st.pop_back();
            b = st.back();
            st.pop_back();
            st.push_back(calcNum(b, a, s[i]));  // 注意计算顺序
            // 每计算一次输出一次计算过程
            for (list<int>::iterator it = st.begin(); it != st.end(); ++it)
                cout << *it << ' ';             // 输出栈中已计算过的数
            for (register int j = i + 1; j < s.size(); j ++)
                cout << s[j] << ' ';            // 再输出还未计算过的数
            cout << endl;
        }
    }
}

经过这三步就完成了题目的要求,所以AC代码如下:

AC代码:

#include <iostream>
#include <cctype>
#include <cmath>
#include <vector>
#include <string>
#include <stack>
#include <list>
#include <algorithm>
using namespace std;

/* 返回每个运算符对应的优先级 */
inline int priority(const char& ch) {
    switch(ch) {
    // 加减的优先级最低,我们设为1
    case '+':
    case '-':
        return 1;
    // 乘除的优先级其次,设为2
    case '*':
    case '/':
        return 2;
    // 注意这里有乘方运算,而样例里没有,最开始我就栽在这里了QwQ
    // 乘方的优先级最高,设为3
    case '^':
        return 3;
    // 括号优先级另行处理,这里可以不写
    case '(':
    case ')':
        return 0;
    // 防止意外情况导致RE,default可写可不写
    default:
        return -1;
    }
}

/* 将中缀表达式转换为后缀表达式 */
inline string toSuffix(const string& s) {
    string ret = "";    // 用于返回的后缀表达式
    stack<char> st;     // 用于实现转换的栈
    for (register int i = 0; i < s.size(); i ++) {  // 枚举每一位
        if (isdigit(s[i])) {                // 如果为数字,直接输出到结果中
            ret += s[i];
        } else if (s[i] == '(') {           // 如果为左括号,直接加入栈中
            st.push(s[i]);
        } else if (s[i] == ')') {           // 如果为右括号,
            while (st.top() != '(')         // 一直弹出并输出直到遇到左括号,
                ret += st.top(), st.pop();  // 并弹出左括号
            st.pop();
        } else {                            // 否则即为普通的运算符,
            while (!st.empty() && priority(st.top()) >= priority(s[i]))
                ret += st.top(), st.pop();  // 一直弹出并输出直到遇到比当前优先级低的运算符,
            st.push(s[i]);                  // 最后把当前运算符压入栈中
        }
    }
    while (!st.empty())             // 最后如果栈中还有剩余的字符,直接弹出并输出
        ret += st.top(), st.pop();
    return ret;                     // 最后返回结果
}

/* 根据运算符计算相应的运算结果 */
inline int calcNum(const int& a, const int& b, const int& symbol) {
    switch(symbol) {
    case '+':
        return a + b;
    case '-':
        return a - b;
    case '*':
        return a * b;
    case '/':
        return a / b;
    case '^':
        return (int)pow(a, b);
    default:    // 与上面同理,default可写可不写
        return -1;
    }
}

/* 将后缀表达式加上空格后输出 */
inline void printSuffix(const string& s) {
    for (register int i = 0; i < s.size(); i ++)
        cout << s[i] << ' ';
    cout << endl;
}

/* 计算后缀表达式并输出计算过程 */
inline void calc(const string& s) {
    /**
     * 实现计算的数字栈,为了输出方便(不需要将每个元素弹出)
     * 并且为了使压入和弹出操作的时间复杂度降到O(1),
     * 这里没有使用vector而是使用了list(链表)
     */
    list<int> st;
    printSuffix(s);     // 注意要先输出后缀表达式,我之前栽在这里了QwQ
    for (register int i = 0; i < s.size(); i ++) {
        if (isdigit(s[i])) {    // 如果遇到数字直接压入栈
            st.push_back(s[i] - '0');
        } else {                // 否则计算结果并压入栈
            register int a, b;  //取出栈顶的两个元素
            a = st.back();
            st.pop_back();
            b = st.back();
            st.pop_back();
            st.push_back(calcNum(b, a, s[i]));  // 注意计算顺序
            // 每计算一次输出一次计算过程
            for (list<int>::iterator it = st.begin(); it != st.end(); ++it)
                cout << *it << ' ';             // 输出栈中已计算过的数
            for (register int j = i + 1; j < s.size(); j ++)
                cout << s[j] << ' ';            // 再输出还未计算过的数
            cout << endl;
        }
    }
}

int main() {
    string s;
    cin >> s;
    calc(toSuffix(s));
    return 0;
}