OI學習筆記10:字尾表示式
阿新 • • 發佈:2021-09-06
字尾表示式
一、定義
1、中綴表示式。
- 中綴表示式是人類善於、也是最常用的一種表示式形式,通常被描述為 \(A\) \(op\) \(B\),其中 \(op\) 為運算子。
- 例如:\(1-(3+2)*2\) 就是一種較複雜的中綴表示式。
2、字尾表示式。
- 字尾表示式是計算機最能理解的表示式形式。由於計算機的堆疊結構,使得其能夠快速地求解字尾表示式。
- 字尾表示式被描述為 \(A\) \(B\) \(op\),其中 \(op\) 為運算子。
- 例如:\(1\ 2 + 3\ *\) 就是一種較為簡單的字尾表示式。
- 更學術地,我們有關於字尾表示式的如下定義:
- 如果 \(E\) 是一個運算元,則 \(E\)
的字尾表示式是其本身。- 如果 \(E\) 是 \(E_1\ op\ E_2\) 形式的表示式,其中 \(op\) 是任何二元操作符,且優先順序不高於 \(E_1\)、\(E_2\) 中括號外的操作符,則 \(E\) 的字尾表示式為 \(E'_1\ E'_2\ op\),其中 \(E'_1\)、\(E'_2\) 分別是 \(E_1\)、\(E_2\) 的字尾表示式。
- 如果 \(E\) 是 \(E_1\) 形式的表示式,則 \(E_1\) 的字尾表示式就是 \(E\) 的字尾表示式。
二、字尾表示式相關演算法
1、字尾表示式求值。
(1)演算法流程。
- 給定字尾表示式,求其值。我們可以用棧來 \(O(N)\)
- 建立一個用於存數的棧,逐一掃描該字尾表示式中的元素。
- 如果遇到一個數,則把該數入棧。
- 如果遇到運算子,就取出棧頂的兩個數進行計算,把結果入棧。
- 掃描完成後,棧中恰好剩下一個數,就是該字尾表示式的值。
(2)程式碼實現。
int getValue(string s){//s為字尾表示式 stack<int> st;//存數字的棧 for(int i = 0; i < s.length(); i++){ if(isdigit(s[i])) st.push(s[i] - '0'); else{ int n2 = st.top(); st.pop();//彈數字 int n1 = st.top(); st.pop(); //計算 if(s[i] == '+') st.push(n1 + n2); if(s[i] == '-') st.push(n1 - n2); if(s[i] == '*') st.push(n1 * n2); if(s[i] == '/') st.push(n1 / n2); if(s[i] == '^') st.push(pow(n1, n2)); } } return st.top(); }
2、中綴表示式轉字尾表示式。
(1)演算法流程。
- 建立一個用於存運算子的棧,逐一掃描該中綴表示式中的元素。
1.如果遇到一個數,輸出該數。
2.如果遇到左括號,把左括號入棧。
3.如果遇到右括號,不斷取出棧頂並輸出,直到棧頂為左括號,然後將左括號出棧。
4.如果遇到運算子,只要棧頂符號的優先順序不低於新符號,就不斷取出棧頂並輸出,最後把新符號入棧。優先順序為乘除\(>\)加減\(>\)左括號。 - 依次取出並輸出棧中所有的剩餘符號,最終輸出的序列就是一個與原中綴表示式等價的字尾表示式。
(2)程式碼實現。
int lev(char c){//返回符號的優先順序,此函式可以使用 map<char. int> 代替
if(c == '+' || c == '-') return 1;
if(c == '*' || c == '/') return 2;
if(c == '^') return 3;
if(c == ')' || c == '(') return 0;
return -1;
}
string toSuffix(const string &s){//轉換
string ret = "";
stack<char> st;
for(int i = 0; i < s.length(); 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() && lev(st.top()) >= lev(s[i]))
ret += st.top(), st.pop();
st.push(s[i]);
}
}
while(!st.empty())
ret += st.top(), st.pop();
return ret;
}
三、例題。
P1175 表示式的轉換
平常我們書寫的表示式稱為中綴表示式,因為它將運算子放在兩個運算元中間,許多情況下為了確定運算順序,括號是不可少的,而中綴表示式就不必用括號了。
字尾標記法:書寫表示式時採用運算緊跟在兩個運算元之後,從而實現了無括號處理和優先順序處理,使計算機的處理規則簡化為:從左到右順序完成計算,並用結果取而代之。
例如:8-(3+2*6)/5+4
可以寫為:8 3 2 6*+5/-4+
。
其計算步驟為:8 3 2 6 * + 5 / – 4 + 8 3 12 + 5 / – 4 + 8 15 5 / – 4 + 8 3 – 4 + 5 4 + 9
編寫一個程式,完成這個轉換,要求輸出的每一個數據間都留一個空格。
一道中綴表示式轉字尾表示式加字尾表示式計算的板子題,具體在程式碼中體會:
#include<cstdio>
#include<bits/stdc++.h>
#include<list>
using namespace std;
inline int lev(char c){
if(c == '+' || c == '-') return 1;
if(c == '*' || c == '/') return 2;
if(c == '^') return 3;
if(c == ')' || c == '(') return 0;
return -1;
}
inline int power(int a, int b){
int ans = 1;
while(b){
if(b & 1) ans *= a;
a *= a;
b >>= 1;
}
return ans;
}
inline string toSuffix(const string &s){//中綴表示式轉字尾表示式
string ret = "";
stack<char> st;
for(int i = 0; i < s.length(); 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() && lev(st.top()) >= lev(s[i]))
ret += st.top(), st.pop();
st.push(s[i]);
}
}
while(!st.empty())
ret += st.top(), st.pop();
return ret;
}
inline int calcNum(int n1, int n2, char s){
if(s == '+') return n1 + n2;
else if(s == '-') return n1 - n2;
else if(s == '*') return n1 * n2;
else if(s == '/') return n1 / n2;
else if(s == '^') return power(n1, n2);
}
inline void calc(string s){//計算並輸出過程
int tail = s.length();
list<int> st;
for(int i = 0; i < tail; i++){
printf("%c ", s[i]);
}
printf("\n");
for(int i = 0; i < tail; i++){
if(isdigit(s[i])){
st.push_back(s[i] - '0');
}
else{//這裡計算使用更為方便的list,而不是使用stack,使用stack不便於輸出過程。
int n2 = st.back(); st.pop_back();
int n1 = st.back(); st.pop_back();
st.push_back(calcNum(n1, n2, s[i]));
for(list<int>::iterator it = st.begin(); it != st.end(); it++)
printf("%d ", *it);//輸出迭代器。
for(int j = i + 1; j < tail; j++)
printf("%c ", s[j]);
printf("\n");
}
}
}
int main(){
string b;
cin>>b;
calc(toSuffix(b));
return 0;
}