• 四则运算表达式


    四则运算表达式

    基本要求

    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]!=''; 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] = '';
            s = rpn;
            return rpn;
    	}
    

    得到后缀表达式之后我们就可以计算后缀表达式的值,也是相应中缀表达式的值。

    计算方法:

    从左到右遍历后缀表达式的每一个数字和运算符号,其中用栈来存储数字。如果遇到数字则将其压入栈中;如果遇到的是运算符,则将栈顶的两个数字取出做相应的运算,并将运算结果压入栈中,直到跑完整个后缀表达式。

    上述的计算方法适用于没有分数的方式,但本项目要求适应分数形式,需要对上述计算方法进行调整,即进行除法运算的时候需要先判断是否做除法的两个数均为整数,若为整数则将结果保存为分数形式,若其中有一个微分数形式就需要做相应的运算,并将结果压入栈中。

    代码实现如下:

    	NUM res()
    	{
    		NUM num[maxn];
    		int j = 0;
    		for(int i=0;s[i]!='';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应用。

  • 相关阅读:
    虚函数和纯虚函数
    MS CRM 2011中PartyList类型字段的实例化
    MS CRM 2011的自定义与开发(12)——表单脚本扩展开发(4)
    MS CRM 2011的自定义与开发(12)——表单脚本扩展开发(2)
    MS CRM 2011的自定义和开发(10)——CRM web服务介绍(第二部分)——IOrganizationService(二)
    MS CRM 2011 SDK 5.08已经发布
    MS CRM 2011 Q2的一些更新
    最近很忙
    Microsoft Dynamics CRM 2011最近的一些更新
    补一篇,Update Rollup 12 终于发布了
  • 原文地址:https://www.cnblogs.com/syncCN/p/5243970.html
Copyright © 2020-2023  润新知