課程設(shè)計題目:表達式求值
一、問題描述與基本要求
利用棧,實現(xiàn)以下功能:
- 中綴表達式求值;
- 中綴表達式轉(zhuǎn)后綴表達式;
- 后綴表達式求值。
假設(shè)用戶輸入的表達式均合法,且運算符只含+(加號)、-(減號)、*、/、%、(和),允許浮點數(shù)(浮點數(shù)計算取模規(guī)定為向零取整后得到整數(shù)再計算取模)。
二、概要設(shè)計
1. 數(shù)據(jù)結(jié)構(gòu)的設(shè)計
我們利用棧來實現(xiàn)上述3個功能。具體原因見后文算法的設(shè)計。
2. 算法的設(shè)計
2.1 表達式讀入與預(yù)處理
用戶從鍵盤輸入一個語法正確的中綴/后綴表達式,但格式可能并不標(biāo)準,例如有多余的空格。另外,為了在后面計算表達式編碼更方便,我想利用C++中的istringstream,所以要將表達式存儲在字符串中,且數(shù)與數(shù)、數(shù)與符號、符號與符號之間都有空格,最后在字符串末尾加上#(原因見下)。
用length表示輸入的中綴表達式字符串長度,則
| 時間復(fù)雜度 | 空間復(fù)雜度 |
|---|---|
2.2 計算中綴表達式
先定義運算符的優(yōu)先級。注意運算符在棧內(nèi)和棧外的優(yōu)先級是不同的,這是為了實現(xiàn)同優(yōu)先級能從左往右計算。
| 運算符 | + - | * / % | ( | ) | # ; |
|---|---|---|---|---|---|
| isp(棧內(nèi)優(yōu)先級) | 3 | 5 | 1 | 6 | 0 |
| icp(棧外優(yōu)先級) | 2 | 4 | 6 | 1 | 0 |
定義#或;,是為了后面統(tǒng)一處理表達式求值。
接下來,我們定義兩個棧:數(shù)值棧(num_stack)和符號棧(sgn_stack)。
從左往右掃描中綴表達式,如果:
- 遇到數(shù)值就直接將它壓入數(shù)值棧;
- 遇到運算符(記當(dāng)前運算符為cur),檢查符號棧的棧頂運算符的優(yōu)先級
- 棧頂優(yōu)先級小于cur,則直接壓入cur
- 棧頂優(yōu)先級大于cur,則彈出棧頂運算符和數(shù)值棧的兩個數(shù)來計算,再把計算結(jié)果壓入數(shù)值棧。重復(fù)直到棧頂運算符的優(yōu)先級不再大于cur,再將cur壓入。
- 棧頂優(yōu)先級等于cur,從上面的運算符優(yōu)先級的表格中我們可以知道這時候棧頂是左括號,cur是右括號,那么直接將這兩個括號扔掉就好了。
- 最后數(shù)值棧里就只剩下一個數(shù),就是整個中綴表達式的計算結(jié)果
如果我們不在原本的中綴表達式后面加#或;,那么還需要把符號棧中的運算符依次彈出來并計算,這無疑讓編碼更加繁瑣更易出錯。所以,不如在后面加上#或;,并把優(yōu)先級設(shè)計成最低(0),統(tǒng)一處理。
偽代碼如下
for (從左到右掃描中綴表達式)
if (cur == 數(shù)值)
num_stack.push(cur)
else // cur == 運算符
while (isp(sgn_stack.top()) > icp(cur))
b = num_stack.top(), num_stack.pop()
a = num_stack.top(), num_stack.pop()
用 sgn_stack.top() 計算 a 和 b,結(jié)果存到 c
num_stack.push(c)
sgn_stack.push(cur)
用n表示輸入的中綴表達式中運算符個數(shù),則
| 時間復(fù)雜度 | 空間復(fù)雜度 |
|---|---|
2.3 中綴表達式轉(zhuǎn)后綴表達式
我們只需要一個符號棧(sgn_stack)就夠了。
從左往右掃描中綴表達式,如果:
- 遇到數(shù)值就直接將它輸出;
- 遇到運算符(記當(dāng)前運算符為cur),檢查符號棧的棧頂運算符的優(yōu)先級
- 棧頂優(yōu)先級小于cur,則直接壓入cur
- 棧頂優(yōu)先級大于cur,則彈出棧頂運算符并輸出。重復(fù)直到棧頂運算符的優(yōu)先級不再大于cur,再將cur壓入。
- 棧頂優(yōu)先級等于cur,從上面的運算符優(yōu)先級的表格中我們可以知道這時候棧頂是左括號,cur是右括號,那么直接將這兩個括號扔掉就好了,因為后綴表達式不需要括號。
同樣,這里在原來的中綴表達式后加上#或;會更方便。
后綴表達式并不需要在最后面也加上#或;(因為它的計算和編碼已經(jīng)夠簡單了)。當(dāng)然,加上也沒問題。
偽代碼如下
for (從左到右掃描中綴表達式)
if (cur == 數(shù)值)
輸出 cur
else // cur == 運算符
while (isp(sgn_stack.top()) > icp(cur))
輸出 sgn_stack.top() sgn_stack.pop()
sgn_stack.push(cur)
用length表示輸入的中綴表達式字符串長度,則
| 時間復(fù)雜度 | 空間復(fù)雜度 |
|---|---|
2.4 計算后綴表達式
后綴表達式的計算就簡單了:遇數(shù)壓棧,遇符就彈兩個數(shù)來算。
for (從左到右掃描后綴表達式)
if (cur == 數(shù)值)
num_stack.push(cur)
else // cur == 運算符
b = num_stack.top(), num_stack.pop()
a = num_stack.top(), num_stack.pop()
用 cur 計算 a 和 b,結(jié)果存到 c
num_stack.push(c)
用n表示輸入的中綴表達式中運算符個數(shù),則
| 時間復(fù)雜度 | 空間復(fù)雜度 |
|---|---|
三、詳細設(shè)計
1. 每個函數(shù)
- 表達式讀入與預(yù)處理:詳見
string _read_expression(); - 計算中綴表達式:詳見
double infix_expression_calc(string infexp); - 中綴表達式轉(zhuǎn)后綴表達式:
string infix_expression_to_postfix_expression(string infexp); - 計算后綴表達式
double postfix_expression_calc(string posexp = _read_expression())。
2. 設(shè)計主函數(shù)
主函數(shù)主要包括對設(shè)計的函數(shù)的測試。
四、運行與測試
1. 測試環(huán)境
運行環(huán)境:Windows 20H2, i7-9750H @ 2.60GHz, 16GB RAM
編譯器:g++ (gcc version 8.1.0 (x86_64-posix-seh-rev0, Built by MinGW-W64 project))
編譯命令:-g
運行終端:cmd
2. 在調(diào)試程序的過程中遇到的問題與解決方案
暫未發(fā)現(xiàn)異常。
3. 設(shè)計的測試數(shù)據(jù)與測試結(jié)果
測試1(測試表達式讀入與預(yù)處理)
樣例1
1+2 * ( 3 - (4-5) /6 )
測試2(測試計算中綴表達式)
樣例2
1+2*(3-(4-5)/6)
樣例3
1.23+4.56
測試3(測試中綴表達式轉(zhuǎn)后綴表達式)
樣例4
1+2*(3-(4-5)/6)
樣例5
1.23+4.56
測試4(測試計算后綴表達式)
樣例6
1 2 3 4 5 - 6 / - * +
樣例7
1.23 4.56 +
4. 程序清單及運行結(jié)果
#include "MyStack.h" // 數(shù)據(jù)結(jié)構(gòu)老師要求自己寫棧,用STL的stack也可
#include <cstring>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
string _read_expression();
double infix_expression_calc(string infexp = _read_expression());
string infix_expression_to_postfix_expression(string infexp = _read_expression());
double postfix_expression_calc(string posexp = _read_expression());
int main()
{
/* test read */
// istringstream iss(_read_expression());
// cout << "length: " << iss.str().length() << "\n";
// char s[20];
// while (iss >> s) cout << s << "\n";
/* test infix_expression_calc */
// cout << infix_expression_calc(_read_expression());
/* test infexp to posexp */
// cout << infix_expression_to_postfix_expression();
/* test postfix_expression_calc */
// cout << postfix_expression_calc(_read_expression());
return 0;
}
inline int _check_ch_type(const char &ch)
{
if (ch >= '0' && ch <= '9' || ch == '.') return 1; // number
if (ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '%' ||
ch == '(' || ch == ')')
return 2; // sign
if (ch == ' ') return 0; // space
return -1; // illegal character
}
string _read_expression()
{
string src; getline(cin, src);
int pre = 0;
vector<char> exp;
for (int i = 0; i < src.length(); i++)
{
int chk = _check_ch_type(src[i]);
if (chk == -1) throw "illegal expression";
if (chk == 0 && exp.back() == ' ') continue;
if (pre * chk >= 2) exp.push_back(' ');
exp.push_back(src[i]);
pre = chk;
}
exp.push_back(' '); exp.push_back('#'); exp.push_back('\0');
return string(exp.begin(), exp.end());
}
double _string_to_double(char *s)
{
int cnt = -100000000;
int len = strlen(s);
double x = 0;
for (int i = 0; i < len; i++, cnt++)
if (s[i] == '.') cnt = -1;
else x = x * 10 + s[i] - '0';
double t = 1;
while (cnt-- > 0) t *= 10;
return x / t;
}
int _get_isp(char ch)
{
if (ch == '+' || ch == '-') return 3;
if (ch == '*' || ch == '/' || ch == '%') return 5;
if (ch == '(') return 1;
if (ch == ')') return 6;
if (ch == '#' || ch == ';') return 0;
return -1;
}
int _get_icp(char ch)
{
if (ch == '+' || ch == '-') return 2;
if (ch == '*' || ch == '/' || ch == '%') return 4;
if (ch == '(') return 6;
if (ch == ')') return 1;
if (ch == '#' || ch == ';') return 0;
return -1;
}
double _calc(double a, char sgn, double b)
{
switch (sgn)
{
case '+': return a + b;
case '-': return a - b;
case '*': return a * b;
case '/': return a / b;
case '%': return (long long)a % (long long)b;
default: throw "illegal sign";
}
}
double infix_expression_calc(string infexp)
{
istringstream is(infexp);
Stack<double> num_stack;
Stack<char> sgn_stack;
char res[20];
while (is >> res)
{
if (res[0] >= '0' && res[0] <= '9')
num_stack.push(_string_to_double(res));
else
{
int _icp = _get_icp(res[0]);
while (!sgn_stack.empty() && _get_isp(sgn_stack.top()) > _icp)
{
double b = num_stack.top(); num_stack.pop();
double a = num_stack.top(); num_stack.pop();
num_stack.push(_calc(a, sgn_stack.top(), b));
sgn_stack.pop();
}
if (!sgn_stack.empty() && _get_isp(sgn_stack.top()) == _icp)
{
sgn_stack.pop();
continue;
}
sgn_stack.push(res[0]);
}
}
return num_stack.top();
}
string infix_expression_to_postfix_expression(string infexp)
{
vector<char> posexp;
Stack<char> sgn_stack;
char buffer[20]; int bufcnt = 0;
for (int i = 0; i < infexp.length(); i++)
{
char &cur = infexp[i];
if (cur == ' ')
{
if (bufcnt)
{
for (int j = 0; j < bufcnt; j++) posexp.push_back(buffer[j]);
posexp.push_back(' ');
bufcnt = 0;
}
continue;
}
if (cur >= '0' && cur <= '9' || cur == '.')
buffer[bufcnt++] = cur;
else
{
int _icp = _get_icp(cur);
while (!sgn_stack.empty() && _get_isp(sgn_stack.top()) > _icp)
{
posexp.push_back(sgn_stack.top());
posexp.push_back(' ');
sgn_stack.pop();
}
if (!sgn_stack.empty() && _get_isp(sgn_stack.top()) == _icp)
{
sgn_stack.pop();
continue;
}
sgn_stack.push(cur);
}
}
posexp.push_back('#'); posexp.push_back('\0');
return string(posexp.begin(), posexp.end());
}
double postfix_expression_calc(string posexp)
{
istringstream is(posexp);
Stack<double> num_stack;
char res[20];
while (is >> res)
{
if (_check_ch_type(res[0]) < 0) break;
if (res[0] >= '0' && res[0] <= '9')
num_stack.push(_string_to_double(res));
else
{
double b = num_stack.top(); num_stack.pop();
double a = num_stack.top(); num_stack.pop();
num_stack.push(_calc(a, res[0], b));
}
}
return num_stack.top();
}
// MyStack.h
template <typename T>
struct Node {
T data;
Node *next;
};
template <typename T>
class Stack {
public:
Stack() {}
~Stack() { for (Node<T> *tmp; _top != nullptr;) { tmp = _top; _top = _top->next; delete tmp; } }
void push(T x) { _top = new Node<T>{x, _top}; }
void pop() { Node<T> *tmp = _top; _top = _top->next; delete tmp; }
T top() { return _top->data; }
bool empty() { return _top == nullptr; }
private:
Node<T> *_top = nullptr;
};
樣例1(符合預(yù)期)
D:\OneDrive - mail2.sysu.edu.cn\MyDocuments\code\DSA\week07\ex3>main
1+2 * ( 3 - (4-5) /6 )
length: 31
1
+
2
*
(
3
-
(
4
-
5
)
/
6
)
#
樣例2(符合預(yù)期)
D:\OneDrive - mail2.sysu.edu.cn\MyDocuments\code\DSA\week07\ex3>main
1+2*(3-(4-5)/6)
7.33333
樣例3(符合預(yù)期)
D:\OneDrive - mail2.sysu.edu.cn\MyDocuments\code\DSA\week07\ex3>main
1.23+4.56
5.79
樣例4(符合預(yù)期)
D:\OneDrive - mail2.sysu.edu.cn\MyDocuments\code\DSA\week07\ex3>main
1+2*(3-(4-5)/6)
1 2 3 4 5 - 6 / - * + #
樣例5(符合預(yù)期)
D:\OneDrive - mail2.sysu.edu.cn\MyDocuments\code\DSA\week07\ex3>main
1.23+4.56
1.23 4.56 + #
樣例6(符合預(yù)期)
D:\OneDrive - mail2.sysu.edu.cn\MyDocuments\code\DSA\week07\ex3>main
1 2 3 4 5 - 6 / - * +
7.33333
樣例7(符合預(yù)期)
D:\OneDrive - mail2.sysu.edu.cn\MyDocuments\code\DSA\week07\ex3>main
1.23 4.56 +
5.79
五、參考資料
- 王紅梅, 胡明, 王濤. 數(shù)據(jù)結(jié)構(gòu)(C++版)[M]. 2 版. 清華大學(xué)出版社, 2011.
- 王紅梅, 胡明, 王濤. 數(shù)據(jù)機構(gòu)(C++版)學(xué)習(xí)輔導(dǎo)與實驗指導(dǎo)[M]. 2 版. 清華大學(xué)出版社, 2011.