表示式求值的雙棧原理
轉載:AcWing 3302. 表示式求值:多圖講解運算子優先順序+詳細程式碼註釋 - AcWing
先看下只有 + 和 * 的。
輸入長度為n的字串,例如:1+2+3*4*5
輸出表達式的值,即:63
應該用什麼資料結構?
棧。
應該先計算哪一步?
實際應該先計算1+2。
“表示式求值”問題,兩個核心關鍵點:
(1)雙棧,一個運算元棧,一個運算子棧;
(2)運算子優先順序,棧頂運算子,和,即將入棧的運算子的優先順序比較:
如果棧頂的運算子優先順序低,新運算子直接入棧
如果棧頂的運算子優先順序高,先出棧計算,新運算子再入棧
仍以1+2+3*4*5舉例,看是如何利用上述兩個關鍵點實施計算的。
首先,這個例子只有+和*兩個運算子,所以它的運算子表是:
這裡的含義是:
(1)如果棧頂是+,即將入棧的是+,棧頂優先順序高,需要先計算,再入棧;
(2)如果棧頂是+,即將入棧的是*,棧頂優先順序低,直接入棧;
(3)如果棧頂是*,即將入棧的是+,棧頂優先順序高,需要先計算,再入棧;
(4)如果棧頂是*,即將入棧的是*,棧頂優先順序高,需要先計算,再入棧;
有了運算子表,一切就好辦了。
一開始,初始化好輸入的字串,以及運算元棧,運算子棧。
一步步,掃描字串,運算元一個個入棧,運算子也入棧。
下一個操作符要入棧時,需要先比較優先順序。
棧內的優先順序高,必須先計算,才能入棧。
計算的過程為:
(1)操作數出棧,作為num2;
(2)操作數出棧,作為num1;
(3)運算子出棧,作為op;
(4)計算出結果;
(5)結果入運算元棧;
接下來,運算子和運算元才能繼續入棧。下一個操作符要入棧時,繼續比較與棧頂的優先順序。
棧內的優先順序低,可以直接入棧。
字串繼續移動。
又要比較優先順序了。
棧內的優先順序高,還是先計算(3*4=12),再入棧。
不斷入棧,直到字串掃描完畢。
不斷出棧,直到得到最終結果3+60=63,演算法完成。
總結
“表示式求值”問題,兩個核心關鍵點:
(1)雙棧,一個運算元棧,一個運算子棧;
(2)運算子優先順序,棧頂運算子,和,即將入棧的運算子的優先順序比較:
如果棧頂的運算子優先順序低,新運算子直接入棧
如果棧頂的運算子優先順序高,先出棧計算,新運算子再入棧
這個方法的時間複雜度為O(n),整個字串只需要掃描一遍。
運算子有+-*/()~^&都沒問題,如果共有n個運算子,會有一個n*n的優先順序表。
程式碼如下:
#include <bits/stdc++.h> using namespace std; typedef long long LL; stack<int> num; stack<char> op; unordered_map<char, int> h{{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}}; void eval() { int a = num.top(); num.pop(); int b = num.top(); num.pop(); char c = op.top(); op.pop(); if (c == '+') num.push(a + b); if (c == '-') num.push(b - a); if (c == '/') num.push(b / a); if (c == '*') num.push(a * b); } int main() { string s; cin>>s; for(int i=0;i<s.size();i++) { if(isdigit(s[i])) { int x=0,j=i; while(j<s.size()&&isdigit(s[j])) { x=x*10+s[j]-'0'; j++; } num.push(x); i = j - 1; } else { if(s[i]=='(') op.push(s[i]); else if(s[i]==')') { while(op.top()!='(') eval(); op.pop(); } else { while(!op.empty()&&h[op.top()]>=h[s[i]]) eval(); op.push(s[i]); } } } while(!op.empty()) eval(); cout<<num.top()<<endl; }