1. 程式人生 > 其它 >OI學習筆記10:字尾表示式

OI學習筆記10:字尾表示式

字尾表示式

一、定義

1、中綴表示式。

  • 中綴表示式是人類善於、也是最常用的一種表示式形式,通常被描述為 \(A\) \(op\) \(B\),其中 \(op\) 為運算子。
  • 例如:\(1-(3+2)*2\) 就是一種較複雜的中綴表示式。

2、字尾表示式。

  • 字尾表示式是計算機最能理解的表示式形式。由於計算機的堆疊結構,使得其能夠快速地求解字尾表示式。
  • 字尾表示式被描述為 \(A\) \(B\) \(op\),其中 \(op\) 為運算子。
  • 例如:\(1\ 2 + 3\ *\) 就是一種較為簡單的字尾表示式。
  • 更學術地,我們有關於字尾表示式的如下定義:
  1. 如果 \(E\) 是一個運算元,則 \(E\)
    的字尾表示式是其本身。
  2. 如果 \(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\) 的字尾表示式。
  3. 如果 \(E\)\(E_1\) 形式的表示式,則 \(E_1\) 的字尾表示式就是 \(E\) 的字尾表示式。

二、字尾表示式相關演算法

1、字尾表示式求值。

(1)演算法流程。

  1. 給定字尾表示式,求其值。我們可以用棧來 \(O(N)\)
    地求出值。
  2. 建立一個用於存數的棧,逐一掃描該字尾表示式中的元素。
    1. 如果遇到一個數,則把該數入棧。
    2. 如果遇到運算子,就取出棧頂的兩個數進行計算,把結果入棧。
  3. 掃描完成後,棧中恰好剩下一個數,就是該字尾表示式的值。

(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. 建立一個用於存運算子的棧,逐一掃描該中綴表示式中的元素。
    1.如果遇到一個數,輸出該數。
    2.如果遇到左括號,把左括號入棧。
    3.如果遇到右括號,不斷取出棧頂並輸出,直到棧頂為左括號,然後將左括號出棧。
    4.如果遇到運算子,只要棧頂符號的優先順序不低於新符號,就不斷取出棧頂並輸出,最後把新符號入棧。優先順序為乘除\(>\)加減\(>\)左括號。
  2. 依次取出並輸出棧中所有的剩餘符號,最終輸出的序列就是一個與原中綴表示式等價的字尾表示式。

(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;
}