利用逆波蘭式(後綴表達式)求解帶括號數學表達式的值

一、前言

本文主要記錄筆者在學習C++適配器相關內容時解決的一道練習題,亦是有拋磚引玉之意~

二、正文

一般我們常用的數學表達式形式,像(a+b)*c等把加減乘除等運算符放在中間的稱呼爲中綴表達式
但是中綴表達式的運算順序受括號影響很大,那有沒有可以無視的括號的表達形式呢?答案是肯定的,那就是逆波蘭式,也叫後綴表達式

引用百度百科的定義:
一個表達式E的後綴形式可以如下定義:
(1)如果E是一個變量或常量,則E的後綴式是E本身。
(2)如果E是E1 op E2形式的表達式,這裏op是任何二元操作符,則E的後綴式爲E1’E2’ op,這裏E1’和E2’分別爲E1和E2的後綴式。
(3)如果E是(E1)形式的表達式,則E1的後綴式就是E的後綴式。

(a + b ) * c的後綴表達式爲 a b + c *,具體轉換算法描述參考逆波蘭表達式

三、算法實現

引用Linux 的創始人 Linus Torvalds的一句名言:Talk is cheap. Show me the code.
下面直接呈上筆者寫的拙劣的算法實現:
1.主函數

//比較運算符優先級
int cpapriority(const string &a, const string &b);
//處理表達式
vector<string> myrpn(istream &in,ostream &out);

int main(){
	for (auto st : myrpn(cin, cout)) {
		cout << st << endl;
		std::istringstream istrm(st);
		string s;
		long double result = 0.0; //保存最終運算結果
		std::stack<long double> stk;
		while (istrm >> s) {
			if (s.find_first_not_of("+-*/") != string::npos) {
				stk.push(std::stod(s));
			}
			else
			{
				auto lval = stk.top(); stk.pop();
				auto rval = stk.top(); stk.pop();
				if (s.find('+') != string::npos) {
					result = lval + rval;
					stk.push(result);
				}
				else if (s.find('-') != string::npos) {
					result = rval - lval;
					stk.push(result);
				}
				else if (s.find('*') != string::npos) {
					result = lval * rval;
					stk.push(result);
				}
				else if (s.find('/') != string::npos) {
					if (rval==0)
					{
						cout << "Divisor can't be zero" << endl;
						return EXIT_FAILURE;
					}
					//後一個數除以前一個數
					result = rval / lval;
					stk.push(result);
				}
			}
		}
		cout << stk.top() << endl;
	}
	return EXIT_SUCCESS; //cstdlib定義的預處理變量,與機器無關。其中EXIT_FAILURE表示失敗
}

2.將中綴表達式轉換爲後綴表達式

vector<string> myrpn(istream &in,ostream &out) {
	//先將中綴表達式轉換爲後綴表達式(逆波蘭式)
	std::stack<string> opstk;  //存放操作符的棧
	//opstk.push("#");
	std::stack<string> valstk; //存放操作數

	vector<string> results;  //存放後綴表達式的結果
	

	string expression;
	//windows按ctrl+z結束輸入,unix/linux按ctrl+d
	while (in >> expression) {
		//(輸入表達式) 方便後面求解
		expression.insert(0, "(");
		expression.push_back(')');
		decltype(expression.size()) index = 0;
		//用於保存多位數
		string val; 
		val.clear();
		try {
			while (index != expression.size())
			{
				auto ch = expression[index];
				auto s = string(1, ch);
				if (ch == '(') {
					//左括號直接入棧
					opstk.push(s);
				}
				else if (ch == ')') {
					if (!val.empty()) {
						valstk.push(val);
						val.clear();
					}
					while (opstk.top() != string(1, '('))
					{
						valstk.push(opstk.top());
						opstk.pop();
					}
					opstk.pop(); //彈出左括號
				}
				else if (s.find_first_of("0123456789")
					!= string::npos) {
					val += ch;
				}
				else if (s.find_first_of("+-*/")
					!= string::npos) {
					if (!val.empty()) {
						//檢測到運算符說明操作數處理完畢
						//此處爲了避')'和運算符'+-*/'相鄰而將操作數入棧兩次
						valstk.push(val); 
						val.clear();
					}
					val.clear();
					while (cpapriority(opstk.top(), s) == 0) {
						valstk.push(opstk.top());
						opstk.pop();
					}
					opstk.push(s);
				}
				else {
					throw - 1;  //出現非法符號
				}
				++index;
			}
			//輸出結果是逆序的,需要處理下
			string result;
			while (!valstk.empty())
			{
				//不斷在頭部插入,加入空格便於區分
				result.insert(0, valstk.top()+" ");
				valstk.pop();
			}
			results.push_back(result); //保存轉換結果
		}
		catch (int errcode) {
			out << "Errorcode:" << errcode
				<< " invalid char";
		}
		catch (...) {
			out << "Unknown Error";
		}
	}
	return results;
}

3.比較運算符優先級

int cpapriority(const string &a, const string &b) {
	//1代表優先級比存儲操作符的棧的棧頂元素優先級高,可直接入棧
	//0代表優先級相同或低,需出棧
	if (a=="(")
	{
		return 1;
	}
	else if (a.find_first_of("+-") 
		!= string::npos) {
		if (b.find_first_of("*/")
			!= string::npos) {
			return 1;
		}
		else
		{
			return 0;
		}
	}
	//else if (a.find_first_of("*/")) {
	else
	{
		return 0;
	}
}

4.測試結果
以下結果僅在windows 10 ,vs2017環境下測試:
在這裏插入圖片描述

四、寫在最後

以上就是本文的全部內容啦,感謝您的閱讀。若有幫助,煩請點個贊吖👍~
害羞!

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