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;
}