目錄
一.問題分析
將用中綴式表示的算術表達式轉換爲用逆波蘭式(後綴式)表示的算術表達式,並計算用逆波蘭式來表示的算術表達式的值.
如輸入如下: 21+(( 42-2) *15+6 ) -18#
輸出爲:
原來表達式: 21+ (( 42-2 ) *15+6 ) - 18#
後綴表達式: 21&42&2&-15&*6&++18&-
計算結果: 609
二.算法思想
1.關於分詞器
將輸入串的所有非空白元素添加到一個vector <string> 數組裏
在輸入串(string)裏設置兩個(廣義上的)指針,一前一後指向非空白元素.遇到空白字符自動向後跳
前 \ 後 |
數字 |
字符 |
數字 |
temp+=input[cur] |
temp+= input[cur] token.push_back(temp) temp.clear() |
字符 |
temp+=input[cur] token.push_back(temp) temp.clear() 比如(5+…) |
假設++,--操作是不存在的 Ep:碰到(3+5)*3 temp+=input[cur] token.push_back(curString) cur.clear() |
2.關於逆波蘭式分析器:
或閱讀該文:逆波蘭分析
三.實現代碼
1.頭文件 & 類視圖
using namespace std;
#include<iostream>
#include<string>
#include<stack>
#include<vector>
#include<algorithm>
/*
逆波蘭式不需要括號,當從中綴式轉換爲RPN時,遇到 ( 括號要暫存之後的運算符;遇到 ) 括號要將內部的運算符(在TempStack)挨個轉移到finStack
逆波蘭計算的是表達式,可以寫一個簡化的分詞器函數(只分析數字,運算符,空格),
將各個長度不一(如"55"長度爲2,"+"長度爲1)的元素分別進入一個 vector<string> token;
這個函數無需分析詞性.分析的工作在覈心函數Analysis()中對token[i][0]判別完成,
Analysis()同時還要調用isSym()來進行判別工作
tempStack 總是將優先級高的運算符放在棧頂,壓住優先級低的運算符.當轉移到finStack時,優先級高的先轉移
*/
class RPN
{
public:
RPN(string);
~RPN();
bool isLegal(); //檢測輸入串是否含有非法字符(不是運算符||操作數的字符)
bool isNum(char); //經過分詞程序後形成的vector<string> token. input:token[i][0] 每個元素的第一個字符
void divideElement(); //分詞器
int Prio(char); //判斷運算符的優先級,數字越小,優先級越弱
void analysis(); //分析程序,將token->逆波蘭式
void Print(); //打印整個逆波蘭式
void Computing(); //根據逆波蘭式計算解
float getRes(); //返回最終值
private:
stack<string>tempExp; //臨時棧,記錄臨時存放的運算符
stack<string>finExp; //最終棧,操作數依序放入即可
vector<string>Result; //記錄fin棧的結果
vector<string>token; //接收分詞後的字符串
string input; //接收輸入的字符串
float res; //接收計算逆波蘭式的最終結果
};
2.預處理部分
包括判斷是否數字,輸入是否合法,將輸入串分詞爲獨立的元素,存入token 數組
/*
isLegal(): 檢測是否存在非法字符,即除了數字和運算符之外的其他所有字符
input: 類數據成員 vector<string> Input
out: 存在非法字符,報錯. (調用isLegal的函數會據此決定是否返回)
*/
bool RPN::isLegal()
{
bool flag = true;
for (size_t i = 0; i < input.size(); i++) //遍歷輸入串的每一個char
{
if ((input[i] >= '0') && (input[i] <= '9')) //判斷數字
{
continue;
}
else //判斷運算符
{
if (input[i] != '+' || input[i] != '-' || input[i] != '*' || input[i] != '/' ||
input[i] != '(' || input[i] != ')' || input[i] != '#' || input[i] != ' ')
{
flag = false;
}
}
}
return flag;;
}
/*
isSym(): 經過isLegal() 處理後,只剩下數字,運算符,空格
return true: 是運算符
return false: 是數字
*/
bool RPN::isNum(char ch)
{
if (ch>='0' &&ch<='9')
{
return true;
}
else
{
return false;
}
}
/*
divideElement(): 將輸入串分爲一個個不含空格的元素,存儲在一個vector<string>裏
input: 成員變量:input
output: 成員變量: vector<string> token
*/
void RPN::divideElement()
{
size_t next = 1; //一開始,指向input[1]
string temp; //臨時存放字符
for (size_t cur = 0; cur < input.size(); cur++,next++) //cur從[0]->[n-2]
{
if (next == input.size() - 1) { next--; }//當next到達最後一個,cur到達倒數第二個時.讓next回退 1,可以有效阻止range溢出
if (input[cur] == ' ' || input[cur] == '\t') //跳過空格
{
cur++;
next = cur + 1; //next總指向cur下一個
}
if (input[next] == ' '|| input[next] == ' \t')
{
next++;
}
if (isNum(input[cur]))//第一個是數字
{
if (isNum(input[next])) //第二個是數字
{
temp += input[cur]; //將前一位添加到temp
if (cur == input.size() - 1) //當到達末尾時
{
token.push_back(temp);
temp.clear();
}
}
else //第二個是字符
{
temp += input[cur]; //前一個進入temp
token.push_back(temp); //token添加這個temp
temp.clear(); //清空temp,待留下次使用
}
}
else
{
temp += input[cur];
token.push_back(temp);
temp.clear();
}
}
}
3.逆波蘭分析過程
包括優先級判斷函數,以及核心的逆波蘭分析程序
/*
Prio(): 對每個字符進行判別
input: token[i][0],即token每個元素的第一個字符
return: 該字符的優先級
*/
int RPN::Prio(char ch)
{
switch (ch)
{
case '#':return 0;
case'+':
case'-':return 1;
case'*':
case'/':
case'×':return 2;
case'(':
case')':return 3;
default:
break;
}
return 0;
}
/*
analysis(): 程序的核心部分,進行逆波蘭式的轉換
input(): token向量,temp棧,fin棧
return: finStack
*/
void RPN::analysis()
{
tempExp.push("#"); //先將優先級最低的"#"放在棧底
for (size_t i = 0; i < token.size(); i++) //遍歷token
{
if (isNum(token[i][0])) //要麼是數字
{
finExp.push(token[i]);
}
else //要麼是運算符
{
if (token[i] == "(") //若x是'(',則直接壓入temp
{
tempExp.push(token[i]);
}
else if (token[i] == ")") //若x是')',則將距離棧s1棧頂的最近的'('之間的運算符,逐個出棧,依次壓入棧fin,
{
while (tempExp.top() != "(")
{
finExp.push(tempExp.top());
tempExp.pop();
}
tempExp.pop(); //此時拋棄 "("
}
else //此時不是( 與 )
{
if (tempExp.top()=="(") //此時temp棧頂是(,直接將掃描字符壓入temp,因爲()內是優先級最高的
{
tempExp.push(token[i]);
}
else //棧頂元素不爲'('
{
if (Prio(token[i][0])>Prio(tempExp.top()[0])) //如果 token[i]的優先級 > temp棧頂的優先級,直接壓入
{
tempExp.push(token[i]);
}
else
{
while (Prio(token[i][0]) <= Prio(tempExp.top()[0]) && tempExp.top()!="(") //否則,不斷地將temp的棧頂轉移到fin(不要括號),直到( temp的棧頂 的優先級 小於 token[i] 的優先級 或 棧頂爲"("
{
finExp.push(tempExp.top());
tempExp.pop();
}
tempExp.push(token[i]);
}
}
}
}
}
//檢查tempExp是否爲空,否則依次轉移到finExp
if (!tempExp.empty())
{
while (tempExp.top()!="#")
{
finExp.push(tempExp.top());
tempExp.pop();
}
}
}
4.計算,輸出部分
包括將棧中的元素取出到一個vector,逆波蘭式計算值
/*
Print: 打印結果
input: finExp棧
return: 一串cout結果
*/
void RPN::Print()
{
//倒騰數據結構
while (!finExp.empty())
{
Result.push_back(finExp.top());
finExp.pop();
}
//想要逆轉過來
reverse(Result.begin(), Result.end());
//逆序打印
for (size_t i = 0; i <Result.size(); i++)
{
cout << Result[i] << " ";
}
cout << endl;
}
void RPN::Computing()
{
while (Result.size()!=1) //這樣寫比較耗費性能,容器物理上的增刪需要花費額外的時間與內存.但是使用指針式的寫法需要更多時間與精力,遂作罷
{
for (size_t i = 0; i < Result.size(); i++)
{
if (!isNum(Result[i][0])) //需要找到第一個運算符
{
float temp=0.0;
switch (Result[i][0])
{
case '+':
temp = stof(Result[i - 2]) + stof(Result[i - 1]);
//新值放在原運算符的位置上
break;
case '-':
temp = stof(Result[i - 2]) - stof(Result[i - 1]);break;
case '/':
temp = stof(Result[i - 2]) / stof(Result[i - 1]);break;
case'*':
case'×':
temp = stof(Result[i - 2]) * stof(Result[i - 1]);
break;
default:
break;
}
Result[i] = to_string(temp); //新值放在原運算符的位置上
Result.erase(Result.begin() + i - 2); //刪除前兩個操作數
Result.erase(Result.begin() + i - 2);
i = 0; //每次執行完後重新從首元素開始
}
}
}
res=stof(Result[0]);
}
float RPN::getRes()
{
return this->res;
}
5.mian()部分
int main()
{
string str="21+(( 42-2)*15+6 ) -18";
cout << "表達式: 21+(( 42-2)*15+6 ) -18\n";
RPN rpn(str);
//輸入串判別
if (!rpn.isLegal())
{
cout << "含有非法字符,請重新輸入\n";
//此處可以有一個goto語句
}
//分詞
rpn.divideElement();
//分析
rpn.analysis();
//打印結果
rpn.Print();
//計算值
rpn.Computing();
cout<<rpn.getRes();
system("pause");
return 0;
}
四.總結
其實最後的計算值部分應該用傳址的方式寫的,但那樣需要更多的時間與精力,時間上不允許優化效率了.