算術表示式求值(棧的應用)
算術表示式求值的難點
運算子有優先規則
對於一般的算術表示式a*b+(c-d/e)*f來說,如果要對它求值,在計算機取到×的時候,不能直接運算,需要繼續往後取值,找是否有比×運算級高的符號,沒有的話才能運算。
運算子的運算元是否能直接操作
而且還不能保證它的兩個運算元都是可以直接操作的,比如針對上式的+來說,它的兩個運算元a*b和(c-d/e)*f是不能直接操作的,需要進一步計算a*b和(c-d/e)*f的值
這些難點導致計算機直接用算術表示式來計算值的不方便,而將算術表示式轉換成字尾表示式將解決以上難點
字尾表示式
概念
字尾表示式,指的是不包含括號,運算子放在兩個運算物件的後面,所有的計算按運算子出現的順序,嚴格從左向右進行(不再考慮運算子的優先規則)。
正是因為沒有優先規則,因此可以根據運算子的先後出現順序來進行計算。
特點
在一般算術表示式中不同運算子優先順序高的在字首表示式中是放在後面的,以a*b+(c-d/e)f為例,它的字尾表示式ab*cde/-f *+。
用位置前後來表達運算子的優先順序,因此不再考慮運算子優先規則。
轉換演算法
將一般算術表示式轉換成字尾表示式。(在這裡用字母a, b, c, ……來代替數字,便於判斷。如果是數字,那麼每次遇到數字,需要將這個數字串輸出)
(1) 首先構造一個運算子棧,運算子(以括號為分界點)在棧內遵循越往棧頂優先順序不降低的原則進行排列。
(2)從左向右掃描算術表示式,從左邊第一個字元開始判斷:
a.如果當前字元是字母,則直接輸出。
b.如果是運算子(不包括括號),則比較優先順序。
①如果是空棧,直接入棧;
②如果當前運算子的優先順序大於棧頂運算子的優先順序,則將運算子直接入棧;
注:對於優先順序相同的運算子來說,先出現的先運算,所以優先順序相等的情況是要先將棧頂元素出棧再將當前元素入棧。
③否則將棧頂運算子出棧並輸出,直到當前運算子的優先順序大於棧頂運算子的優先順序or棧頂是左括號時,再將當前運算子入棧。
注:括號比任何在括號前面入棧的元素優先順序都大,比任何在左括號之後的,右括號之前的優先順序都小。因為括號之中的運算子優先順序最大
c.如果是括號,則根據括號的方向進行處理。
①如果是左括號,則直接入棧;
②否則,遇右括號前將所有的運算子全部出棧並輸出,遇左括號後將左右的兩括號一起刪除。
(3) 重複上述操作(2)直至掃描結束,將棧內剩餘運算子全部出棧並輸出。
注:看到這裡的逆序輸出就明白為什麼之前棧內運算子是優先順序增加的原則排列了吧。
中綴表示式也就轉換為字尾表示式了。
以下程式碼假設不存在非法輸出,以’#’為終止符
string Swap (string s)
{
string temp;
Stack operators;
operators.Push('#');
bool flag = true;
for (int i = 0; flag; i++)
{
char t = s[i];
//如果是操作符,就和棧頂元素比較,如果優先順序高,就入棧,否則將棧頂元素輸出,再將該操作符壓入棧中
//如果是運算元,就直接輸出。
switch(t)
{
case '(':
{
operators.Push(t);
break;
}
case ')':
{
char c = operators.Pop();
while (c != '(')
{
temp += c;
c = operators.Pop();
}
break;
}
case '*':
case '/':
{
//只要棧頂元素不是'*'和'/',就要入棧
char c = operators.Pop();
while (c == '*' || c == '/')
{
temp += c;
c = operators.Pop();
}
operators.Push(c);
operators.Push(t);
break;
}
case '+':
case '-':
{
//只要棧頂元素不是'('和'#',就要先將棧頂元素出棧
char c = operators.Pop();
while (c != '(' && c != '#')
{
temp += c;
c = operators.Pop();
}
operators.Push(c);
operators.Push(t);
break;
}
case '#':
{
//輸入結束,將棧中元素統統出棧
char c = operators.Pop();
while (c != '#')
{
temp += c;
c = operators.Pop();
}
flag = false;
break;
}
default:
{
//運算元
temp += t;
break;
}
}
}
return temp;
}
字尾表示式值的計算
使用一個棧s來進行值的計算。
從左向右掃描字尾表示式,遇到運算元壓入s棧,遇到運算子,將棧頂的2個元素出棧計算,再將結果壓入s棧;直到結束,將棧中唯一的元素出棧。
int Calculation (string s)
{
Stack stack;
for (int i = 0; s[i] != '#'; i++)
{
if (s[i] >= '0' && s[i] <= '9')
{
stack.Push((int)(s[i] -48));
continue;
}
else
{
int a, b;
a = stack.Pop();
b = stack.Pop();
if (s[i] == '+')
stack.Push(a+b);
else if (s[i] == '-')
stack.Push(b-a);
else if (s[i] == '*')
stack.Push(b*a);
else if (s[i] == '/')
stack.Push(b/a);
}
}
return stack.Pop();
}
字首表示式
概念
字首表示式是一種沒有括號的算術表示式,其將運算子寫在前面,運算元寫在後面。為紀念其發明者波蘭數學家Jan Lukasiewicz,字首表示式也稱為“波蘭式”。
對於一般的算術表示式
a*b+(c-d/e)f,它的字首表示式是:+*ab-c/def
(a+b)(c+d),它的字首表示式是:+ab+cd
a+b,它的字首表示式是:+,a,b
a+(b-c),它的字首表示式是:+,a,-,b,c
a+(b-c)d ,它的字首表示式是:+,a,,-,b,c,d
a+1+3 ,它的字首表示式是: +,+,a,1,3
特點
可以看出在一般算術表示式中不同運算子優先順序高的在字首表示式中是放在後面的,以a*b+(c-d/e)*f為例,‘()’裡的兩個運算子優先順序最高,而同樣在‘()’中的‘ / ’優先順序比‘ - ’高,所以‘ / ’在最後,‘ - ’在‘ / ’前面。在’ + ’ 兩側的運算式中都有‘ × ’,在一般算術表示式中,‘ × ’優先順序高於‘ + ’,在字首表示式中,‘ × ’是在‘+’前面的。
轉換演算法
將一般算術表示式轉換成字首表示式。(在這裡用字母a, b, c, ……來代替數字,便於判斷。如果是數字,那麼每次遇到數字,需要將這個數字串輸出)
(1) 首先構造一個運算子棧s1,運算子(以括號為分界點)在棧內遵循越往棧頂優先順序不降低的原則進行排列。再建立一個儲存中間結果的棧s2
(2)從右至左掃描算術表示式,從右邊第一個字元開始判斷:
a.如果當前字元是字母,則壓入s2。
b.如果是運算子(不包括括號),則比較優先順序。如果當前運算子的優先順序大於等於s1棧頂運算子的優先順序,則將運算子直接入棧s1;否則將s1棧頂運算子出棧s1並壓入s2,直到當前運算子的優先順序大於等於s1棧頂運算子的優先順序(當棧頂是右括號時,直接入棧),再將當前運算子入棧s1。
注:括號比任何在括號前面入棧的元素優先順序都大,比任何在右括號之後的,左括號之前的優先順序都小。
c.如果是括號,則根據括號的方向進行處理。如果是右括號,則直接入棧s1;否則,遇左括號前將所有的運算子全部出棧壓入s2,遇右括號後將左右的兩括號一起刪除。
(3) 重複上述操作(2)直至掃描結束,將棧內剩餘運算子全部出棧s1並壓入s2,再將s2中所有元素出棧。
注:看到這裡的s2棧就明白為什麼之前s1棧內運算子是優先順序增加的原則排列了吧。經過s1出來的運算子以優先順序高的在前面的順序壓入s2,再由s2輸出的話就會變成優先順序高的在後面了。
中綴表示式也就轉換為字首表示式了。
字首表示式值的計算
使用一個棧進行值的儲存
從右向左掃描,如果是運算元,壓入棧中;如果是運算子,取出棧頂的2個元素進行運算,將值壓入棧中;直到結束,輸出棧中的唯一元素。
字首表示式和字尾表示式區別
對於+ - or × /這種優先順序相同的運算子的處理。
在字首表示式的轉換中,遇到運算子優先順序相等的情況是入棧的。
在後綴表示式的轉換中,遇到運算子優先順序相等的情況是出棧的。
原因在於,字尾表示式是從左向右掃描的,在優先順序相同的時候,是先進棧的運算子先運算;
而字首表示式是從右向左掃描的,在優先順序相同的時候,是後進棧的運算子先運算。
所以,在運算子棧中的操作,對於字尾表示式來說,只要不大於棧頂元素,就要將棧頂元素出棧,而字首表示式是不大於等於棧頂元素,才將棧頂元素出棧。
可以看到字首和字尾表示式在求表示式值的時候比一般表示式方便的多
中綴表示式
概念
運算子在運算元之間
與一般算術表示式有區別,中綴表示式無法正確還原一般算術表示式,依舊以a*b+(c-d/e)*f為例,中綴表示式a*b+c-d/e*f