算数表达式的计算

一、算数表达式的两种表示


           在计算机中进行算数表达式的计算是通过栈来实现的。算数表达式的两种表示方法:即中缀表达式和后缀表达式。

把双目运算符出现在两个操作数中间的这种习惯表示叫做算数表达式的中缀表示,这种算数表达式被称为中缀算数表达式或中缀表达式。(就是平常我们习惯的表示方式)

       例如:2+5*6  

中缀表达式的计算必须遵守以下三条规则:

(1) 先计算括号内,后计算括号外。

(2) 在无括号或同层括号内,先进行乘除运算后进行加减运算。

(3)同一优先级运算,从左向右依次进行。

        把运算符放在两个运算对象后面,采用后缀表示的算术表达式被称为后缀算术表达式或后缀表达式。在后缀表达式中,不存在括号,也不存在运算符优先级的差别,计算过程完全按照运算符出现的先后次序进行,整个计算过程仅需扫描一遍即可完成。

        中缀算术表达式转换成对应的后缀算术表达式的规则是:把两个运算符都移到它的两个运算对象的后面,并用空格填补原来运算符位置,然后删除掉所有的括号即可。

例如,对于下列各中缀表达式

(1)3/5+6

(2)16-9*(4+3)

(3)2*(x+y)/(1-x)

(4)(25+x)*(a*(a+b)+b)

对应的后缀表达式分别为:

(1)3 5/ 6+

(2)16 9 4 3+*-

(3)2 x y+* 1 x - /

(4)25 x + a a b+* b+ *


二、后缀表达式求值的算法

1. 后缀表达式的求值算法

        后缀表达式的求值,扫描一遍即可完成。它需要使用一个栈,假定用sck表示,其元素类型应为操作数的类型,假定为浮点型double,用此栈存储后缀表达式中的操作数、计算过程中的中间结果以及最后结果。后缀表达式求值算法的基本思路是:对于一个以后缀算术表达式为内容的字符串,每次从该字符串中读入一个字符,若它是空格则不做任何处理,若它是运算符,则表明它的两个操作数已经在栈sck中,其中栈顶元素为运算符的后一个操作数,栈顶元素的前一个操作数,把它弹出后进行相应运算并保存到一个双精度变量(假定为x)中,否则,扫描到的字符必为数字或小数点,应把从此开始的双精度浮点数字符串转换为一个浮点数并存入x中,然后把计算或转换得到的浮点数(即x的值)压入到sck栈中,依次向下扫描每一个字符并进行上述处理,当处理后缀表达式结束后,最终结果保存在栈中,并且栈中仅存这一个值,把它弹出返回即可。整个算法描述为:

//计算字符串str中的后缀表达式的值
	public static double compute(String str)
	{
		Stack sck=new SequenceStack();              //用sck栈存储操作数和中间计算结果
		double x,y;                                 //定义x,y用于保存浮点数
		char [] a=str.toCharArray();                //定义a用于保存由str转换得到的字符数组,字符串中的内容被赋给字符数组a中
		int i=0;
		//扫描后缀表达式中的每个字符,并进行相应处理
		while(i<a.length)
		{
			while(a[i]==' ')
			{
				i++;                                //扫描到空格字符不做任何处理
			}
			switch(a[i])                            //对其他字符分情况处理
			{
			    case '+':                           //做栈顶两个元素的加法,和赋给x
				     x=(Double)sck.pop()+(Double)sck.pop();
				     i++;
				     break;
			    case '-':                           //做栈顶两个元素的减法,差赋给x
			    	x=(Double)sck.pop();            //弹出减数
			    	x=(Double)sck.pop()-x;          //弹出被减数并做减法
			    	i++;
			    	break;
			    case '*':                           //做栈顶两个元素的乘法,积赋给x
			    	x=(Double)sck.pop()*(Double)sck.pop();
			    	i++;
			    	break;
			    case '/':                           //做栈顶两个元素的除法,商赋给x
			    	x=(Double)sck.pop();            //弹出除数
			    	if(Math.abs(x)>le-6)
			    	{
			    		x=(Double)sck.pop()/x;      //弹出被除数并计算
			    	}
			    	else                            //除数为0时终止运行
		    		{
		    			System.out.println("除数为0,退出运行");
		    			System.exit(1);
		    		}
			    	i++;
			    	break;
			    	default:                         //扫描到的若是浮点数字符串,生成对应的浮点数
			    		if((a[i]<'0'||a[i]>'9')&&a[i]!='.')
			    		{
			    			System.out.println("非法字符,退出运行!");
			    			System.exit(1);
			    		}
			    		x=0.0;                       //利用x保存扫描到的整数部分的值
			    		while(a[i]>=48 && a[i]<=57)  //48是字符0的ASCLL码    
			    		{
			    			x=x*10+a[i]-48;
			    			i++;
			    		}
			    		if(a[i]=='.')                //转换小数部分
			    		{
			    			i++;
			    			y=0.0;                   //利用y保存扫描到的小数部分的值
			    			double j=10.0;           //用j作为相应小数位的权值
			    			while(a[i]>=48 && a[i]<=59)
			    			{
			    				y=y+(a[i]-48)/j;
			    				i++;
			    				j*=10;
			    			}
			    			x+=y;                    //把小数部分合并到整数部分x中
			    		}
			}//switch语句结束 
			sck.push(x);                             //把扫描转换后或进行相应运算后得到的一个浮点数压入栈中
		}//while语句结束
		if(sck.isEmpty())                            //若计算结束后栈为空则终止运行
		{
			System.out.println("栈为空,退出运行!");
			System.exit(1);
		}
		x=(Double)sck.pop();  
		if(!sck.isEmpty())                           //若栈中仅有一个元素,则它就是后缀表达式的值,否则为出错
		{
			System.out.println("表达式格式错误,退出运行!");
			System.exit(1);
		}
		return x;                                    //返回后缀表达式的值
	}

        此算法的运行时间主要花费在while( i<a.length )循环上,它从头到尾扫描后缀表达式中的每一个字符,若后缀表达式的字符串长度为n,则此算法的时间复杂度为O(n)。此算法在运行时所占用的临时空间主要取决于栈sck的大小,显然,它的最大深度不会超过表达式中所含操作数的个数,因为操作数的个数远比n小,所以此算法的空间复杂度不超过O(n)。


2. 把中缀表达式转换为后缀表达式的算法

       设中缀算术表达式已经保存在具有String类型的s字符串中,转换后得到的后缀算术表达式拟存于可改变的字符串类型(StringBuffer)对象sb中,算法结束时再转换为一般的字符串类型(String)的对象返回。由中缀表达式转换为后缀表达式的规则可知:转换前后,表达式中的数值项的次序不变。而运算符的次序发生了变化,由处在两个运算对象的中间变为处在两个运算对象的后面,同时去掉了所有的括号。为了使转换正确,必须设定一个运算符栈,并在栈底开始放入一个特殊运算符,假定为‘@’字符,让它具有最低的运算符优先级,假定其优先级设定为数值0,此栈用来保存扫描中缀表达式所得到的暂不能放入后缀表达式中的运算符,待它的两个运算对象都放入到后缀表达式以后,在令其出栈写入到后缀表达式中。

       把中缀表达式转换为后缀表达式算法的思路是:从头到尾扫描中缀表达式中的每个字符(以‘@’作为结束标记),对于不同类型的字符按不同情况进行处理。若遇到的是空格则认为是分隔符,不需要进行任何处理;若遇到的是数字或小数点,则直接写入到s2中,并在每个数值的后面写入一个空格;若遇到的是中缀表达式结束符‘@’,则退出循环处理过程,接着进行结束和返回处理;若遇到的是左括号,则应把它压入到运算符栈中,待以它开始的括号内的表达式转换完毕后再出栈;如遇到的是右括号,则表明括号内的中缀表达式已经扫描完毕,把从栈顶直到保存着的对应左括号之间的运算符依次退栈并写入到sb串的结尾处;若遇到的是运算符,当该运算符的优先级大于栈顶运算符的优先级(加减运算符的优先级设定为1,乘除运算符的优先级设定为2,在栈底保存的特殊运算符‘@’和左括号运算符‘(’的优先级设定为0)时,表明该运算符的后一个运算对象还没有被扫描并放入到sb串中,应把它暂存于运算符栈中,待它的后一个运算对象从s串中读出并写入到sb串之后,再令其出栈并写入sb串中;若遇到的运算符的优先级小于等于栈顶运算符的优先级,表明栈顶运算符的两个运算对象已经被保存到sb串中,应将栈顶运算符退栈并写入到sb串找那个,对于新的栈顶运算符仍继续进行比较和处理,直到被处理的运算符的优先级大于栈顶运算符的优先级为止,然后再将该运算符进栈。

       按照以上过程扫描到中缀表达式字符串的结尾符‘@’时,应把栈中剩余的运算符依次退出栈并写入到后缀表达式中,整个转换过程就处理完毕,在sb中就得到了转换后的后缀表达式,把它转换成一般的字符串对象返回即可。

      将中缀算术表达式转换为后缀算术表达式的算法描述如下():

//将字符串s中保存的中缀表达式(以‘@’结尾)转换为后缀表达式并返回
	public static String change(String s)
	{
		Stack sck=new SequenceStack();     //定义用于暂存运算符的栈sck,它可以为顺序,也可以为链接
		sck.push("@");                     //给栈底放入‘@’字符,它具有最低优先级
		char [] a=s.toCharArray();         //将s中保存的中缀表达式转换为字符数组a的内容,以方便处理
		StringBuffer sb=new StringBuffer();//定义和创建一个可变化的字符串类型对象sb,初始内容为空
		int i=0;                           //定义i用来指示数组a中被处理元素的下标
		char ch=a[i];                      //定义ch用来指示数组a中被处理元素的元素值
		//依次处理中缀表达式中的每个字符
		while(i<a.length)
		{
			while(ch==' ')                 //对于空格字符不做任何处理,循环读取下一个字符
			{
				ch=a[++i];
			}
			if(ch=='@')                    //如果访问到表达式结束符‘@’则退出循环,做算法的结束处理
			{
				break;
			}
			if(ch=='(')                    //对于左括号,直接进栈
			{
				String ch1 = String.valueOf(ch);
				sck.push(ch1);
				ch=a[++i];
			}
			else if(ch==')')               //对于右括号,使括号内的仍停留在栈中的运算符依次出栈并写入sb结尾  
			{
				//while((Character)sck.peek()!='(')
				while(!sck.peek().equals("("))
				{
					sb.append(sck.pop());
					sck.pop();              //删除栈顶的左括号
					ch=a[++i];
				}
			}
			//对于运算符,使栈顶且不低于ch优先级的运算符依次出栈并写入sb结尾
			else if(ch=='+' || ch=='-' || ch=='*' || ch=='/')
			{
				char w=(Character)sck.peek();
				while(precedence(w)>=precedence(ch))
				{
					//precedence(w)函数返回运算符形参ch的优先级
					sb.append(sck.pop());     //出栈
					w=(Character)sck.peek();  //读取新的栈顶元素
				}
				sck.push(ch);                 //把ch运算符写入栈中
				ch=a[++i];
			}
			//此处必然为数字或小数点字符,否则为中缀表达式错误
			else
			{
				//若ch不是数字或小数点字符则退出运行
				if((ch<'0' || ch>'9')&&(ch!='.'))
				{
					System.out.println("中缀表达式表示错误!");
					System.exit(1);
				}
				//把一个数值中的每一位依次写入到sb的字符串的末尾
				while((ch>='0'&&ch<='9')||ch=='.')
				{
					sb.append(ch);
					ch=a[i++];
				}
				//在sb的每个数值后面放入一个空格字符
				sb.append(' ');
			}
		}
		//做算法的结束处理,把暂存在栈中的运算符依次退栈并写入到sb末尾
		ch=(Character)sck.pop();
		while(ch!='@')
		{
			if(ch=='(')
			{
				System.out.println("表达式错误!");
				System.exit(1);
			}
			else
			{
				sb.append(ch);
				ch=(Character)sck.pop();
			}
		}
		//把可变化的字符串对象sb转换为String对象并返回
		return new String(sb);
	}
	
    //在上面的算法中用到了precedence(char op)方法,它返回运算符op的优先级,该方法的具体定义为:
	public static int precedence(char op)
	{
		switch(op)
		{
		    case '+':
		    case '-':
		    	return 1;                 //定义加减运算的优先级为1
		    case '*':
		    case '/':
		    	return 2;                 //定义乘除运算的优先级为2
		    case '(':
		    case '@':
		    	default:
		    		return 0;             //定义在栈中的左括号和栈底字符的优先级为0
		}
	}

       在上面的中缀转后后缀的算法中,中缀算术表达式中的每个字符均需要扫描一遍,对于从s中稻苗得到的每个运算符,最多需要进行sck栈、出sck栈和写入sb后缀表达式这3次操作,对于从s中扫描得到的每个数字或小数点,只需要把它直接写入到sb后缀表达式即可。所以,此算法的时间复杂度为O(n),n为后缀表达式中字符的个数。该算法需要使用一个运算符栈,需要的深度不会超过中缀表达式中运算符的个数,所以此算法的空间复杂度之多也为O(n)。



  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值