利用逆波兰式(后缀表达式)求解带括号数学表达式的值

一、前言

本文主要记录笔者在学习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环境下测试:
利用逆波兰式(后缀表达式)求解带括号数学表达式的值_第1张图片

四、写在最后

以上就是本文的全部内容啦,感谢您的阅读。若有帮助,烦请点个赞吖~
利用逆波兰式(后缀表达式)求解带括号数学表达式的值_第2张图片

你可能感兴趣的:(C++)