編譯原理 | 實驗四 | 逆波蘭式

目錄

 

一.問題分析

二.算法思想

1.關於分詞器

2.關於逆波蘭式分析器:

三.實現代碼

1.頭文件  &  類視圖

2.預處理部分

 3.逆波蘭分析過程

4.計算,輸出部分

四.總結 


一.問題分析

將用中綴式表示的算術表達式轉換爲用逆波蘭式(後綴式)表示的算術表達式,並計算用逆波蘭式來表示的算術表達式的值.

如輸入如下:     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;
}

 

 

四.總結 

其實最後的計算值部分應該用傳址的方式寫的,但那樣需要更多的時間與精力,時間上不允許優化效率了.

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章