zoukankan      html  css  js  c++  java
  • 四则运算表达式

    四则运算表达式

    基本要求

    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应用。

  • 相关阅读:
    干货分享:如何使用Kubernetes的Ingress API
    十年OpenStack Ussuri最新版发布 主要改进在可靠性、安全性和用例支持等方面
    如何更好地优化容器的创建?这些技巧你务必收藏
    Kubernetes是容器化微服务的圣杯么?
    微服务是否真的需要服务网格?
    ZOOM火速收购加密公司Kaybase 能否补齐安全短板?
    5个实例告诉您:如何实施成功的容器化多云策略
    新基建火了,开源云计算渠道能做什么?
    盘点6个Kubernetes监视工具
    掌握这10种方法帮你快速在Linux上分析二进制文件
  • 原文地址:https://www.cnblogs.com/syncCN/p/5243970.html
Copyright © 2011-2022 走看看