四则运算表达式

四则运算表达式

基本要求

  1. 加减乘除四种运算全部出现
  2. 算式中要出现括号
  3. 出现真分数和假分数的运算
  4. 最少出现一个长度为10的四则运算(10个数字的混合运算)

扩展要求

  1. 实现四则运算算式的自动生成
  2. 把程序变成一个网页程序
  3. 把程序变成一个Windows/Mac/Linux 电脑图形界面的程序
  4. 把程序变成一个智能手机程序

阶段性成果

完成基本要求

四则运算表达式求值的过程,可分为两个模块,即:

  • 中缀表达式转后缀表达式
  • 后缀表达式计算

同时由于程序中存在真分数与假分数的情况,即将所有数字均视为分子/分母的形式,如整数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; 
}

实现四则运算算式的自动生成

四则运算表达式生成的难点在于判重,因为数字是随机生成,会有一定的可能性导致两个运算表达式等价,而我没有找到很好的办法比较两个运算表达式是否等价,考虑过以下思路:

  1. 有序生成表达式,以升序或降序的方式生成数字组成表达式,可以良好地避免产生重复的算术表达式问题,但这样的操作会导致有部分算术表达式永远不会被生成出来。
  2. 随机生成表达式中有几个数,随机生成这几个数字,同时随机生成数字之间的运算符号(不包括括号),最后再随机生成括号,这样做不能完全规避重复的问题,但在要产生的表达式较少且较短的时候不会出现重复的现象,满足题目所需。

我采用的是第二种思路,但是每产生一个数字(除了最后一个数字)便随机生成后面所跟着的运算符是什么,同样考虑到左括号出现在数字的前面和右括号出现在数字后面,所以在生成数字前后随机生成一个标志位用来控制要不要生成括号,之后要保证括号的合法性,即左右括号要配对,同时不能括在同一个数字上。

代码实现如下:

		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编辑  收藏  举报

导航