题解 P1175 【表达式的转换】
0AND1STORY
2019-07-15 16:31:28
这道题的解题思路很简单:
1. 把中缀表达式转换为后缀表达式(使用栈)
2. 根据后缀表达式计算结果(使用栈)
这两个过程都可以用栈来实现,所以实现过程就很清晰了。
### 实现过程:
#### 1. 读入中缀表达式:
因为输入的字符串长度不定,这里用`string`计算相对方便一些。
```cpp
string s;
cin >> s;
```
#### 2. 转换后缀表达式:
转换后缀表达式主要使用**栈**来实现,只需要枚举每一位,分情况处理即可。
一共有四种情况,如下:
- 数字:直接输出即可。
- 左括号:直接加入栈中。
- 右括号:一直弹出栈中元素并输出,直到遇到左括号为止。最后**弹出**左括号**但不输出**左括号。
- 一般运算符:一直弹出栈中元素并输出,直到遇到比自己运算优先级**低**的运算符。最后把当前运算符压入栈中。
最后若计算完成但**栈中还有元素**,弹出栈中所有元素并输出。
输出的字符串即为转换完成的后缀表达式。
```cpp
/* 返回每个运算符对应的优先级 */
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. 计算后缀表达式的值,并输出计算过程:
计算后缀表达式也使用栈来实现,计算过程同理要分情况处理。
一共分为两种情况,如下:
- 数字:直接压入栈即可。
- 运算符:取出栈顶的两个数进行相应的运算,将计算结果压入栈中。**一定要注意取出元素的计算顺序!先取出的数为`X数`(X为减/除/etc.),后取出的数为`被X数`!**每次完成当前计算后输出计算过程。
输出计算过程的方法为:
1. 先输出已计算完成的数,即所有的栈中元素(**需要按照从栈底到栈顶的顺序**)。
2. 再输出还未计算的数,即字符串中当前字符到末尾的字符串。
因为需要输出栈中元素,为了方便,不需要将每个元素弹出后输出,并且使每次压入和弹出的操作的时间复杂度降到尽可能的小,这里没有使用`stack`(每次需弹出)和`vector`(常数相对较大)而使用了`list`(链表)~~(也可以说我有点强迫症吧2333)~~。
```cpp
/* 根据运算符计算相应的运算结果 */
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代码:
```cpp
#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;
}
```