实例图解表达式求解--栈的应用(C++)


2019/1/15 怀着激动的心情


表达式求解有多种方法,如可将中缀表达式转换为后缀表达式,再利用一个栈处理即可。本文主要介绍利用两个栈直接处理表达式的方法

为了便于理解算法思想,接下来将以图解方式详细解释算法每一步过程。不过在此之前还得做些预备工作,了解运算符间的优先级关系(这个极其重要)

在这里插入图片描述
具体的算法描述网络到处都是,我这里就不赘述了,直接看例子

实例图解

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
最后仅有操作数栈存在唯一的元素即运算结果,求解结束。这个求解过程应当是容易理解的,接下来看看如何实现叭~

程序使用范围

1) 运算数为实数
2) 运算符为+、-、*、/、(、)、#
3) 运算结果为实数

设计流程

主要分为三步
1,表达式预处理
2,建立运算符优先表
3,运算求值

1) 表达式预处理

文件中读取一行,去除所有空格,并在表达式首尾各添加一个符号‘#’,表示(作为起止标记)

//表达式预处理:从文件中读出表达式,同时去除所有空格、并在头尾各加一个字符‘#’ 
void InitExpression(string &s)
{
	//从文件中读取表达式,并将去除所有空格,在头尾各加一个‘#’字符的字符串存入s中 
	fstream inFile("测试表达式.txt",ios::in);
	if(!inFile)cout<<"文件打开失败!"<<endl;
	
	char ch;
	s += '#';
	while(true)
	{
		inFile>>ch;//去空格,换行读取 
		if(!inFile)break;//这两行位置不可换,否则最后一个字符会多读一次 
		s += ch;	 
	}
	s += '#';
	inFile.close(); //关闭文件,好习惯 
} 

文件测试表达式.txt内容

100.234 + 2.258 +(-1.43)*(-3)*(-2000.6)+(-9-4/2+3)*2/2-1-9/1/3/3 +   1 + 28*2*2/56/2*(-10.24)/100.43

2) 建立运算符优先表

四则运算法则

  • 先乘除,后加减
  • 先左后右
  • 先括号内,后括号外

文件运算优先级.txt的内容

+ - * / ( ) #
> > < < < > >
> > < < < > >
> > > > < > >
> > > > < > >
< < < < < = $
$ $ $ $ $ > >
< < < < < $ =
  • 第一行为运算符
  • 剩余7行是优先关系,其中$代表关系不存在/无意义;左右括号优先级相等,井号与井号优先级相等,井号作用类似括号

  • 从文件读取并以运算符ASCII码作为下标建立关系表
  • 因为运算符个数有限,所以可用下标映射,类似哈希映射,也与哈夫曼编码实现思想异曲同工
void CreateRelation(char relation[255][255])//直接传递二维数组等价于传址 
{
	fstream inFile("运算优先级.txt",ios::in);
	if(!inFile)cout<<"文件打开失败!"<<endl;
	
//	vector<int> optr;//存储7个运算符的ASCII码 
	string s;//从文件读取一行 
	getline(inFile,s);//读取第一行,操作符(运算符) 
	for(int i = 0; i < s.size(); i++)//将符号转换为数值 
	{
		if(s[i] == ' ')continue;//跳过空格 
		int a = s[i];//cout<<"s[i]:"<<s[i]<<"  a: "<<a<<endl;
		optr.push_back(a);//为何不能一边推入数值,一边输出???? 
	}
//	char relation[255][255];//存储运算符优先级,运算符的ASCII码作为下标 
	//将运算符关系存储于关系数组 
	for(int i = 0; i < optr.size(); i++)
	{
		getline(inFile,s);
		int k = 0;//计算s位置 
		for(int j = 0; j < optr.size(); j++)
		{
			if(s[k] == ' ')k++;//每次最多一个空格 
			relation[optr[i]][optr[j]] = s[k++];
		}
	}
	inFile.close();
} 

3) 运算求值

实数的处理

实数包含三个部分:符号位,整数部分,小数部分
符号位判断:当前为数值,前一位为“-”/“+”,前两位为“(”,是负数/正数

运算符号处理

假设运算符号a1在a2前,二者优先级共三种情况,分别对应不同处理

  • a1 < a2,a2压入符号栈
  • a1 = a2,弹出符号栈栈顶
  • a1 < a2,弹出符号栈栈顶a1,弹出数值栈两个元素b,a(注意顺序)与a1运算(a a1 b),得到的结果压入式数值栈

实数的三个部分处理时一定细心;运算符三个分支把握好

//该判断用得多,干脆封装为函数
//判断是否为操作数。是->true;不是->false 
bool IsOpnd(char ch)
{
	int a = ch - '0';
	if(a>=0 && a<=9)return true;
	else return false;
}
//表达式求值 
void EvaluateExpression()
{
	char relation[255][255];
	CreateRelation(relation);//创建运算优先表 
	
	string s;
	InitExpression(s);//表达式预处理 
	cout<<s;
	
	//========开始处理表达式============= 
	stack<double> opnd;//数值栈 
	stack<char>	optr;//运算符栈 
	optr.push(s[0]);//'#'压入,作为标记 
	
	int pos = 1;//记录s的位置 
	while(!optr.empty())
	{	int fix = 1;//符号位 
		if(IsOpnd(s[pos]))//是操作数 
		{
			double a = s[pos] - '0';//字符转化为ASCII码(整型) 
			//处理符号位:判断正负 
			if(pos>=3 && s[pos-2] == '(' && s[pos-1] == '-')//一元操作符(-19) 
			{
				fix = -1;
				optr.pop();
			}
			else if(pos>=3 && s[pos-2] == '(' && s[pos-1] == '+')
			{
				fix = 1;
				optr.pop();
			}
			//============处理数值 ================
			while(IsOpnd(s[pos+1]))//处理整数部分 
			{
				int t = s[pos+1] - '0';
				a = a*10 + t;
				pos++;
			}
			if(s[pos+1] == '.')//处理小数部分 
			{
				pos++;
				int count = 1;//计算到小数点的距离
				while(IsOpnd(s[pos+1]))
				{
					double t = s[pos+1] - '0';
					for(int i = 0; i < count; i++)
					{
						t = t/10;
					}
					a += t;
					count++;
					pos++; 
				}
			}
			a = a*fix;
//			cout<<a<<endl;
			opnd.push(a);//重新压入 
			pos++;
		}
		else//操作符 
		{
			char ch1,ch2,r;
			ch1 = optr.top();//栈顶运算符 
			ch2 = s[pos];
			
			int a1,a2;//转化为数值 
			a1 = ch1;
			a2 = ch2;
			
			r = relation[a1][a2];//ch1,ch2关系,注意二者顺序,在栈里的在前面!!! 
			if(r == '<')//ch1<ch2,运算符直接入栈
			{
				optr.push(ch2);
				pos++;
			} 
			else if(r == '=')
			{
				optr.pop();
				pos++;
			}
			else if(r == '>')
			{
				char tch = optr.top();
				optr.pop();
				
				double a,b;//注意出栈顺序 
				b = opnd.top();opnd.pop();
				a = opnd.top();opnd.pop();
				
				double result = 0;
				if(tch == '+')
				{
					result = a + b;
				}
				else if(tch == '-')
				{
					result = a - b;
				}
				else if(tch == '*')
				{
					result = a * b;
				}
				else if(tch == '/')
				{
					result = a / b;
				}
				opnd.push(result);//结果入数值栈 
				//不需要pos++,当前字符继续判断即可 
			}
		} 
	}
	cout<<endl<<"  结果:"<<opnd.top()<<endl;
} 

总结体会

  • 打开电脑前把要做的事想得有7分明白,这次做得不错,基本思路都已成熟,所以写出程序很快。为啥是7分明白呢?因为不足7分思路混乱,bug出现概率极高,而且难以调试,可谓磨刀不误砍柴工;为啥不是8分,9分甚至10分呢?一是几乎不可能把你第一次接触的问题想个透彻,二是太浪费时间了。中庸之道,带着7分理解,如此次我已设计好三个基本流程;在实际编码中发现之前未注意到的细节,写代码时才发现需要字符串处理,由于一开始为考虑实数处理,导致卡在实数处理上2个小时,oh天呐!这样bug少,时间短,效率极高。
  • 写代码就像盖房子,先有好的架构,再落实到一砖一瓦上,地基不稳,地动山摇。为了避免出现问题无从下手,没写一个功能都测试一下,依次迭代开发,效率较高
  • 从文本文件读出一行字符串是包括空格的,需要处理
  • 做有个字符串的题目,字符处理是最核心的,别以为他核心逻辑简单,但是字符处理逻辑复杂呀,而且千体千面,不想其他算法,模板稍微改改就成,这个得自己仔细分析,一种情况都漏不得
  • 不知是不是写代码时间持续太长,导致之前明明很清楚,写对的代码到后来自己又改错了。以后一定要适当休息,身体第一

完整源码

#include<iostream>
using namespace std;
#include<stack>
#include<string>
#include<fstream>
#include<vector>
vector<int> optr;
void CreateRelation(char relation[255][255])//直接传递二维数组等价于传址 
{
	fstream inFile("运算优先级.txt",ios::in);
	if(!inFile)cout<<"文件打开失败!"<<endl;
	
//	vector<int> optr;//存储7个运算符的ASCII码 
	string s;//从文件读取一行 
	getline(inFile,s);//读取第一行,操作符(运算符) 
	for(int i = 0; i < s.size(); i++)//将符号转换为数值 
	{
		if(s[i] == ' ')continue;//跳过空格 
		int a = s[i];//cout<<"s[i]:"<<s[i]<<"  a: "<<a<<endl;
		optr.push_back(a);//为何不能一边推入数值,一边输出???? 
	}
	for(int i = 0; i < optr.size(); i++)
	{
	//		cout<<optr[i]<<" ";
	}
//	char relation[255][255];//存储运算符优先级,运算符的ASCII码作为下标 
	//将运算符关系存储于关系数组 
	for(int i = 0; i < optr.size(); i++)
	{
		getline(inFile,s);
		int k = 0;//计算s位置 
		for(int j = 0; j < optr.size(); j++)
		{
			if(s[k] == ' ')k++;//每次最多一个空格 
			relation[optr[i]][optr[j]] = s[k++];
		}
	}
	inFile.close();
/*	for(int i = 0; i < optr.size(); i++)
	{
		for(int j = 0; j < optr.size(); j++)
		{
			cout<<relation[optr[i]][optr[j]]<<" ";
		}
		cout<<endl;
	}
*/	
} 
//判断是否为操作数。是->true;不是->false 
bool IsOpnd(char ch)
{
	int a = ch - '0';
	if(a>=0 && a<=9)return true;
	else return false;
}
//表达式预处理:从文件中读出表达式,同时去除所有空格、并在头尾各加一个字符‘#’ 
void InitExpression(string &s)
{
	//从文件中读取表达式,并将去除所有空格,在头尾各加一个‘#’字符的字符串存入s中 
	fstream inFile("测试表达式.txt",ios::in);
	if(!inFile)cout<<"文件打开失败!"<<endl;
	
	char ch;
	s += '#';
	while(true)
	{
		inFile>>ch;//去空格,换行读取 
		if(!inFile)break;//这两行位置不可换,否则最后一个字符会多读一次 
		s += ch;	 
	}
	s += '#';
	inFile.close(); //关闭文件,好习惯 
} 
//表达式求值 
void EvaluateExpression()
{
	char relation[255][255];
	CreateRelation(relation);//创建运算优先表 
	
	string s;
	InitExpression(s);//表达式预处理 
	cout<<s;
	
	//开始处理表达式 
	stack<double> opnd;//数值栈 
	stack<char>	optr;//运算符栈 
	optr.push(s[0]);//'#'压入,作为标记 
	
	int pos = 1;//记录s的位置 
	while(!optr.empty())
	{	int fix = 1;//符号位 
		if(IsOpnd(s[pos]))//是操作数 
		{
			double a = s[pos] - '0';//字符转化为ASCII码(整型) 
			//处理符号位:判断正负 
			if(pos>=3 && s[pos-2] == '(' && s[pos-1] == '-')//一元操作符(-19) 
			{
				fix = -1;
			//	a = -a;cout<<"-a:"<<a<<endl;
			//	pos++;
				optr.pop();
			}
			else if(pos>=3 && s[pos-2] == '(' && s[pos-1] == '+')
			{
				fix = 1;
			//	a = a;
			//	pos++;
				optr.pop();
			}
			//处理数值 
			while(IsOpnd(s[pos+1]))//处理整数部分 
			{
				int t = s[pos+1] - '0';
				a = a*10 + t;
				pos++;
			}
			if(s[pos+1] == '.')//处理小数部分 
			{
				pos++;
				int count = 1;
				while(IsOpnd(s[pos+1]))
				{
					double t = s[pos+1] - '0';
					for(int i = 0; i < count; i++)
					{
						t = t/10;
					}
				//	if(a < 0) t = -t; 
					a += t;
					count++;
					pos++; 
				}
			}
			a = a*fix;
//			cout<<a<<endl;
			opnd.push(a);//重新压入 
			pos++;
		}
		else//操作符 
		{
			char ch1,ch2,r;
			ch1 = optr.top();//栈顶运算符 
			ch2 = s[pos];
			
			int a1,a2;//转化为数值 
			a1 = ch1;
			a2 = ch2;
			
			r = relation[a1][a2];//ch1,ch2关系,注意二者顺序,在栈里的在前面!!! 
			if(r == '<')//ch1<ch2,运算符直接入栈
			{
				optr.push(ch2);
				pos++;
			} 
			else if(r == '=')
			{
				optr.pop();
				pos++;
			}
			else if(r == '>')
			{
				char tch = optr.top();
				optr.pop();
				
				double a,b;//注意出栈顺序 
				b = opnd.top();opnd.pop();
				a = opnd.top();opnd.pop();
				
				double result = 0;
				if(tch == '+')
				{
					result = a + b;
				}
				else if(tch == '-')
				{
					result = a - b;
				}
				else if(tch == '*')
				{
					result = a * b;
				}
				else if(tch == '/')
				{
					result = a / b;
				}
				opnd.push(result);//结果入数值栈 
				//不需要pos++,当前字符继续判断即可 
			}
		} 
	}
	cout<<endl<<"  结果:"<<opnd.top()<<endl;
} 
int main()
{
	EvaluateExpression();
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值