前言
最近開始刷題,真實地解決了大學時期“這黑窗口敲來敲去做數學題有卵用?”的困惑。有些東西之前學過,現在忘了,但是正是因為學過,所以再學一遍就變得效率很高(但是我還是不認可大學的學科教學順序)。廢話不多說,這篇博客只是一個筆記,希望之后有了更深的認識能夠完善。
前綴、中綴以及后綴表達式是什么?
先聚合一下定義,以后萬一要復習也好找XD
前綴表達式
波蘭表示法(Polish notation,或波蘭記法),是一種邏輯、算術和代數表示方法,其特點是操作符置于操作數的前面,因此也稱做前綴表示法。
中綴表達式
中綴表示法(或中綴記法)是一個通用的算術或邏輯公式表示方法, 操作符是以中綴形式處于操作數的中間(例:3 + 4)。與前綴表達式(例:+ 3 4)或后綴表達式(例:3 4 +)相比,中綴表達式不容易被電腦解析,但仍被許多程序語言使用,因為它符合人們的普遍用法。
與前綴或后綴記法不同的是,中綴記法中括號是必需的。計算過程中必須用括號將操作符和對應的操作數括起來,用于指示運算的次序。
后綴表達式
逆波蘭表示法(Reverse Polish notation,RPN,或逆波蘭記法),是一種是由波蘭數學家揚·武卡謝維奇1920年引入的數學表達式方式,在逆波蘭記法中,所有操作符置于操作數的后面,因此也被稱為后綴表示法。逆波蘭記法不需要括號來標識操作符的優先級。
對中綴表達式進行轉換
這應該是基礎中的基礎了,理解并記憶思路,再跟著栗子走兩步,最后敲一遍代碼,基本就掌握了。
中綴表達式轉前綴表達式
思路
- 初始化兩個棧:運算符棧
S1
; 操作數棧S2
- 從右至左掃描中綴表達式
- 遇到
操作數
時,將其壓入S2
- 遇到
運算符
時,比較其與S1
棧頂運算符的優先級- 如果<span style="font-weight:bold">
S1
為空</span>,或棧頂運算符為右括號")"
,或其優先級比棧頂運算符的優先級較高或相等,則直接將此運算符
入棧 - 否則,將
S1
棧頂的運算符彈出并壓入到S2
中,再次進行與S1
棧頂運算符的優先級比較
- 如果<span style="font-weight:bold">
- 遇到
括號
時- 如果是
右括號 ")"
,則直接壓入S1
- 如果是
左括號 "("
,則依次彈出S1
棧頂的運算符,并壓入S2
,直到遇到右括號 ")"
為止,此時將這一對括號
丟棄
- 如果是
- 重復步驟 2 至 5,直到表達式的最左邊
- 將
S1
剩余的運算符依次彈出并壓入S2
- 依次彈出
S2
中的元素并輸出,結果即為中綴表達式對應的前綴表達式
栗子
(1 + (3 * 4) / 6 ) - 5
掃描到的元素 | S2 (棧底 -> 棧頂) | S1 (棧底 -> 棧頂) | 說明 |
---|---|---|---|
5 | 5 | 空 |
操作數 ,直接入棧 S2
|
- | 5 | - |
運算符 ,S1 為空,直接入棧 |
) | 5 | - ) |
右括號 ,直接入棧 S1
|
6 | 5 6 | - ) |
操作數 ,直接入棧 S2
|
/ | 5 6 | - ) / |
運算符 ,且 S1 棧頂為 右括號 ,直接入棧 |
) | 5 6 | - ) / ) |
右括號 ,直接入棧 S1
|
4 | 5 6 4 | - ) / ) |
操作數 ,直接入棧 S2
|
* | 5 6 4 | - ) / ) * |
運算符 ,且 S1 棧頂為 右括號 ,直接入棧 |
3 | 5 6 4 3 | - ) / ) * |
操作數 ,直接入棧 S2
|
( | 5 6 4 3 * | - ) / |
左括號 ,S1 棧彈出運算符壓入 S2 直至遇到右括號 ,一對括號丟棄 |
+ | 5 6 4 3 * / | - ) + |
運算符 ,但優先級低于 S1 棧頂運算符,S1 彈出運算符壓入 S2 ,直至棧頂優先級低于運算符,再入棧 |
1 | 5 6 4 3 * / 1 | - ) + |
操作數 ,直接入棧 S2
|
( | 5 6 4 3 * / 1 + | - |
左括號 ,S1 棧彈出運算符壓入 S2 直至遇到右括號 ,一對括號丟棄 |
到達最左端 | 5 6 4 3 * / 1 + - | 空 | 將 S1 剩余的運算符依次彈出并壓入 S2
|
依次彈出 S2
中的元素并輸出結果: -+1/*3465
代碼
中綴表達式轉后綴表達式
思路
- 初始化兩個棧:運算符棧
S1
; 操作數棧S2
- 從左至右掃描中綴表達式
- 遇到
操作數
時,將其壓入S2
- 遇到
運算符
時,比較其與S1
棧頂運算符的優先級- 如果<span style="font-weight:bold">
S1
為空</span>,或棧頂運算符為左括號 "("
,或其優先級比棧頂運算符的優先級較高,則直接將此運算符
入棧 - 否則,將
S1
棧頂的運算符彈出并壓入到S2
中,再次進行與S1
棧頂運算符的優先級比較
- 如果<span style="font-weight:bold">
- 遇到括號時
- 如果是
左括號 "("
,則直接壓入S1
- 如果是
右括號 ")"
,則依次彈出S1
棧頂的運算符,并壓入S2
,直到遇到左括號 "("
為止,此時將這一對括號
丟棄
- 如果是
- 重復步驟 2 至 5,直到表達式的最右邊
- 將
S1
剩余的運算符依次彈出并壓入S2
- 拼接
S2
中的元素并輸出,結果即為中綴表達式對應的后綴表達式
栗子
(1 + (3 * 4) / 6 ) - 5
掃描到的元素 | S2 (棧底 -> 棧頂) | S1 (棧底 -> 棧頂) | 說明 |
---|---|---|---|
( | 空 | ( | 左括號,直接入棧 S1
|
1 | 1 | ( | 操作數,直接入棧 S2
|
+ | 1 | ( + | 運算符,且 S1 棧頂為 左括號,直接入棧 |
( | 1 | ( + ( | 左括號,直接入棧 S1
|
3 | 1 3 | ( + ( | 操作數,直接入棧 S2
|
* | 1 3 | ( + ( * | 運算符,且 S1 棧頂為 左括號,直接入棧 |
4 | 1 3 4 | ( + ( * | 操作數,直接入棧 S2
|
) | 1 3 4 * | ( + | 右括號,S1 棧彈出運算符壓入 S2 直至遇到左括號,一對括號丟棄 |
/ | 1 3 4 * | ( + / | 運算符,且優先級高于 S1 棧頂的運算符,直接入棧 |
6 | 1 3 4 * 6 | ( + / | 操作數,直接入棧 S2
|
) | 1 3 4 * 6 / + | 空 | 右括號,S1 棧彈出運算符壓入 S2 直至遇到左括號,一對括號丟棄 |
- | 1 3 4 * 6 / + | - | 運算符,S1 為空,直接入棧 |
5 | 1 3 4 * 6 / + 5 | - | 操作數,直接入棧 S2
|
到達最右端 | 1 3 4 * 6 / + 5 - | 空 | 將 S1 剩余的運算符依次彈出并壓入 S2
|
拼接 S2
中的元素并輸出結果:134*6/+5-
代碼
考題擴展
leetcode 150. 逆波蘭表達式求值
根據逆波蘭表示法,求表達式的值。
有效的運算符包括 +, -, *, / 。每個運算對象可以是整數,也可以是另一個逆波蘭表達式。
說明:
整數除法只保留整數部分。
給定逆波蘭表達式總是有效的。換句話說,表達式總會得出有效數值且不存在除數為 0 的情況。
對比上面的操作,這題考的是一個逆向思維。
思路
- 初始化一個棧
stack
和一個包含+-*/
操作的操作策略對象operation
- 從左至右掃描逆波蘭表達式
- 遇到操作數時,壓入棧
stack
- 遇到運算符時,依次取出棧頂的兩個元素b、a(為了照顧(減)除法操作,先出棧的為被(減)除數 b ,后出棧的為(減)除數 a ),調用
operation
策略對象的相應方法,并將運算結果入棧stack
- 遇到操作數時,壓入棧
- 重復步驟 2,直到表達式的最右邊
- 彈出棧頂元素即是運算結果
代碼
波蘭表達式求值
這個就舉一反三就完事了。
思路
- 初始化一個棧
stack
和一個包含+-*/
操作的操作策略對象operation
- 從右至左掃描波蘭表達式
- 遇到
操作數
時,壓入棧stack
- 遇到
運算符
時,依次取出棧頂的兩個元素a、b(為了照顧(減)除法操作,先出棧的為被(減)除數 a ,后出棧的為(減)除數 b ),調用operation
策略對象的相應方法,并將運算結果入棧stack
- 遇到
- 重復步驟 2,直到表達式的最左邊
- 彈出棧頂元素即是運算結果
代碼
模擬 eval('(1 + (3 * 4) / 6 ) - 5')
思路
經過上面的熟悉和理解,這個就很簡單了,只要將中綴表達式
轉換成后綴表達式
或者前綴表達式
其中的一種,再進行求值即可,代碼就是把上面的組裝一下,這里就不列出了。
后記
工作一段時間,算法和數據結構漸漸生疏了,在刷題的時候,又把大學的學習的知識慢慢找回來,雖然要花一段時間,但是一旦將時間投入進去,會慢慢地感興趣,進入良性循環。
貼個GitHub
,剛剛起步進行“圣地巡禮”:https://github.com/LazyDuke/leetcode-js。