四则运算表达式
四则运算表达式
基本要求
- 加减乘除四种运算全部出现
- 算式中要出现括号
- 出现真分数和假分数的运算
- 最少出现一个长度为10的四则运算(10个数字的混合运算)
扩展要求
- 实现四则运算算式的自动生成
- 把程序变成一个网页程序
- 把程序变成一个Windows/Mac/Linux 电脑图形界面的程序
- 把程序变成一个智能手机程序
阶段性成果
完成基本要求
四则运算表达式求值的过程,可分为两个模块,即:
- 中缀表达式转后缀表达式
- 后缀表达式计算
同时由于程序中存在真分数与假分数的情况,即将所有数字均视为分子/分母的形式,如整数n看作n/1,这样可以简化相当大一部分代码量。
由此我们可以定义如下数据结构:
struct NUM {
bool type; //记录一下是分数还是整数,在做连续除法时需要
int num1; //分子
int num2; //分母,当数字为整数时,分母为1
};
之后我们要做的就是将中缀表达式转化为后缀表达式。
转换的规则:
从左到右遍历中缀表达式中的每一个数字和运算符号,其中需要用一个栈存储运算符号。如果遇到数字就直接成为后缀表达式的一部分;如果遇到的是运算符号,则需要先与栈顶的运算符号比较优先级,若优先级不大于栈顶运算符号,则将栈顶运算符号逐次出栈,直到此运算符号的优先级为栈中最高,将此运算符号压入栈中;若该运算符为右括号,则将栈顶运算符号依次出栈,直到遇到左括号结束。
例:中缀表达式 9 * ( 8 + 3 ) / 2 - 3 转换为后缀表达式为 9 8 3 + * 2 / 3 -
代码实现如下:
string trans()
{
stack<char> Stack;
Stack.push('(');
char rpn[maxn];
int i=0,j=0;
for(; s[i]!='\0'; i++)
{
if('0'<=s[i] && s[i]<='9')
{
rpn[j++]=s[i];
}
else if(s[i]=='*' || s[i]=='/')
{
rpn[j++]=' ';
if(Stack.top()=='*' || Stack.top()=='/')
{
rpn[j++]=Stack.top();
Stack.pop();
}
Stack.push(s[i]);
}
else if(s[i]=='+' || s[i]=='-')
{
rpn[j++]=' ';
if(Stack.top()=='*' || Stack.top()=='/')
{
rpn[j++]=Stack.top();
Stack.pop();
}
if(Stack.top()=='+' || Stack.top()=='-')
{
rpn[j++]=Stack.top();
Stack.pop();
}
Stack.push(s[i]);
}
else if(s[i]=='(' || s[i]==')')
{
if(s[i]=='(')
{
Stack.push(s[i]);
}
else
{
while(Stack.top()!='(')
{
rpn[j++]=Stack.top();
Stack.pop();
}
Stack.pop();
}
}
}
while(Stack.top()!='(')
{
rpn[j++]=Stack.top();
Stack.pop();
}
rpn[j] = '\0';
s = rpn;
return rpn;
}
得到后缀表达式之后我们就可以计算后缀表达式的值,也是相应中缀表达式的值。
计算方法:
从左到右遍历后缀表达式的每一个数字和运算符号,其中用栈来存储数字。如果遇到数字则将其压入栈中;如果遇到的是运算符,则将栈顶的两个数字取出做相应的运算,并将运算结果压入栈中,直到跑完整个后缀表达式。
上述的计算方法适用于没有分数的方式,但本项目要求适应分数形式,需要对上述计算方法进行调整,即进行除法运算的时候需要先判断是否做除法的两个数均为整数,若为整数则将结果保存为分数形式,若其中有一个微分数形式就需要做相应的运算,并将结果压入栈中。
代码实现如下:
NUM res()
{
NUM num[maxn];
int j = 0;
for(int i=0;s[i]!='\0';i++)
{
if(s[i]>='0' && s[i]<='9')
{
LL tmp = 0;
while('0'<=s[i] && s[i] <= '9')
{
tmp = 10*tmp + s[i] - '0';
i++;
}
num[j].num2 = 1;
num[j++].num1 = tmp;
i--;
}
else if(s[i]=='/')
{
j--;
if(num[j-1].type || num[j].type)
{
num[j-1].num1 *= num[j].num2;
num[j-1].num2 *= num[j].num1;
}
else
{
num[j-1].type = true;
num[j-1].num2 = num[j].num1;
}
}
else if(s[i] == '*')
{
j--;
num[j-1].num1 *= num[j].num1;
num[j-1].num2 *= num[j].num2;
}
else if(s[i]=='+' || s[i]=='-')
{
j--;
if(s[i]=='+')
{
num[j-1].num1 = num[j].num1*num[j-1].num2 + num[j].num2*num[j-1].num1;
num[j-1].num2 *= num[j].num2;
}
else
{
num[j-1].num1 = num[j-1].num1*num[j].num2 - num[j-1].num2*num[j].num1;
num[j-1].num2 *= num[j].num2;
}
}
else if(s[i]!=' ')
cout << "Error" << endl;
}
return num[0];
}
以上两个步骤完成之后,可能得到的形式不是最简形式,需要对分子和分母同时除以他们的最大公约数,得到相应的结果。
求最大公约数的方法采用辗转相除法,代码实现如下(递归形式):
int gcd(int a, int b)
{
return a%b ? gcd(b,a%b):b;
}
实现四则运算算式的自动生成
四则运算表达式生成的难点在于判重,因为数字是随机生成,会有一定的可能性导致两个运算表达式等价,而我没有找到很好的办法比较两个运算表达式是否等价,考虑过以下思路:
- 有序生成表达式,以升序或降序的方式生成数字组成表达式,可以良好地避免产生重复的算术表达式问题,但这样的操作会导致有部分算术表达式永远不会被生成出来。
- 随机生成表达式中有几个数,随机生成这几个数字,同时随机生成数字之间的运算符号(不包括括号),最后再随机生成括号,这样做不能完全规避重复的问题,但在要产生的表达式较少且较短的时候不会出现重复的现象,满足题目所需。
我采用的是第二种思路,但是每产生一个数字(除了最后一个数字)便随机生成后面所跟着的运算符是什么,同样考虑到左括号出现在数字的前面和右括号出现在数字后面,所以在生成数字前后随机生成一个标志位用来控制要不要生成括号,之后要保证括号的合法性,即左右括号要配对,同时不能括在同一个数字上。
代码实现如下:
int len = rand()%8+2;
vector<reg> v;
int lcurve_cnt = 0, lcurve_pos = 0;
reg r;
for(int i=0;i<len;i++)
{
int lcurve = rand()%2;
if(lcurve && i!=len-2 && i!=len-1 && len!=2)
{
lcurve_cnt++;
r.op = '(';
r.num = -1;
v.push_back(r);
lcurve_pos = v.size();
}
LL tmp = rand()%maxn + 1;
r.num = tmp;
v.push_back(r);
int rcurve = rand()%2;
if(rcurve && lcurve_cnt && v.size()-lcurve_pos > 1)
{
lcurve_cnt--;
r.op = ')';
r.num = -1;
v.push_back(r);
}
if(i!=len-1)
{
int choice = rand()%4;
r.op = op[choice];
r.num = -1;
v.push_back(r);
}
}
while(lcurve_cnt--)
{
r.op = ')';
r.num = -1;
v.push_back(r);
}
for(int i=0;i<v.size();i++)
{
if(v[i].num == -1)
cout << v[i].op ;
else
cout << v[i].num ;
}
cout << endl;
近期目标
将本程序移植到C#,同时锻炼C#的运用能力,为之后的项目打好基础,并且完成建立Windows图形界面程序的要求。如果时间富裕,将其搭建成Web应用。
posted on 2016-03-05 00:44 13070046孙宇辰 阅读(626) 评论(2) 编辑 收藏 举报