表达式求值

表达式求值

一.概述

中缀表达式求值可以先将中缀转后缀,再用后缀计算结果,但是,有点太麻烦,而另一种方式是利用两个栈直接求值,中缀表达式求值基本过称为:

定义两个栈,s1存储数字,s2存储运算符
1. 从左到右扫描,如果扫描到的运算符优先级大于栈顶运算符优先级,则入栈s2,否则,出栈并运算:从s1出两个数,s2出一个运算符,将计算后的结果送入s1
2. 如果遇到右括号,一直出栈,直到遇到左括号为止。并且每一次出栈的运算符都要做一次运算。如果遇到左括号则直接压入栈,括号的优先级最高
3. 扫描完整个表达式后,S2栈中如果还有运算符剩余,则全部出栈,逐个计算即可。

二.需要注意的问题

1.如何处理运算符号优先级问题
‘+’/’-’ < ‘*’, ‘/’ , ‘%’ < ‘^’
可以用不同的数字代表不同等级的运算符的优先级

 public static int compare(char x) //确定运算符类型的优先级,同个数值同种优先级,数字大小表示优先级大小,用于下一步的判断
	 {
		 switch(x)
		 {
		 case '+': return 0;
		 case '-': return 0;
		 case '*': return 1;
		 case '/': return 1;
		 case '%': return 1;
		 case '^': return 2;
		 }
		 return -1;
	 }

2.如何比较栈顶的运算符和输入的运算符的优先级大小

public static boolean compare(char a,char b) //比较栈中字符的优先级,判断是否直接压栈还是出栈
	 {
		 if(b=='(') 
			 {
			   return false;  //如果栈顶为左括号,无论什么情况都压入栈
			 }
		 if(compare(a)<=compare(b))
		 {
			 return true;   //可以将栈中的运算符弹出
		 }
		 else return false;  //压入栈中
	 }

3.如何根据运算符对运算栈的数进行运算

 public static void calculate(char x,Stack<Double> st2)//根据x运算符对运算栈的数进行运算
	 {
		 if(st2.size()<2)
		 {
			 System.out.print("1表达式不合法/");
			 System.exit(-1);
		 }
		 else
		 {
			 double a=st2.pop();
			 double b=st2.pop();
			 switch(x)
			 {
			 case '+': st2.push(a+b);
			           break;
			 case '-': st2.push(b-a);
			 		   break;
			 case '*': st2.push(a*b);
			           break;
			 case '/': 
				        if(a==0)
				       {
				    	   System.out.print("表达式不合法,因为除数不能为0");
						   System.exit(-1);
				       }
				       st2.push(b/a);
			 		   break;
			 case '%': 
				   if(is_(a)||is_(b))
		           {
		        	   System.out.print("%运算不能有小数参与!");
		        	   System.exit(-1);
		           }
				       st2.push(b%a);
			           break;
			 case '^': 
				   if(is_(a))
				   {
					   System.out.println("指数不能为小数");
					   System.exit(-1);
				   }
				   st2.push((double)Math.pow(b,a));
			           
			
			 }
			 
		 }
	 }

4.怎么确定数的类型以及数的位数,以及怎么分辨出数和符号
设置两个boolean型的变量:tag(显示是否为小数),get(判断是否有数要读入栈),一个double型的变量sum用于记录当前取到的数值,一个int型的变量r,用于显示小数位.
另外,符号用compare()确定;

  boolean tag=false; //显示是否为小数
	   boolean get=false; //判断是否有数要读入栈
	   double sum=0;
	   int r=1; //显示小数点后有几位数
  for(int i=0;i<s.length();i++)
	   {
		   char x=s.charAt(i);
		   if(x>='0'&&x<='9')  //数字
		   {
			  sum=sum*10+x-'0';
			  get=true;
		   }
		   else if(x=='.')
		   {
			   tag=true;
			   continue;
		   }
		   else if(x=='(')
		   {
			  
		   }
		   else if(x==')')
		   {
			  .
			  .
			  .
			   r=1;
			   tag=false;
			   get=false;
			   sum=0; //对栈里的数字处理完后各个状态的更新
			   .
			   .
			   .
		   }
		   else if(compare(x)!=-1)  //符号
		   {
			   if(get==true) 
			   {
			      if(tag==true)
			      {
			    	  sum=(double)sum/r; //小数的话需要移动小数点
			      }
			      st2.push(sum);
			   }
		       r=1;
		      tag=false;//读取一个数,如果是小数则取消小数的标志位
		      get=false;
			   sum=0;  //更新数
			  .
			  .
			  .
		   }
		   else  //其他不合法的符号
		   {
			  
		   }
		   if(tag==true)  //计算小数点后几位数
		   {
			   r*=10;
		   }
	   }

6.对输入的表达式进行逐一处理,比如扫描到左括号,右括号,数字,符号,非法符号怎么处理.

public static void main(String[] args)
	{
	   Scanner sc=new Scanner(System.in);
	   int flag=0; //检测左括号的数量
	   boolean tag=false; //显示是否为小数
	   boolean get=false; //判断是否有数要读入栈
	   double sum=0;
	   int r=1; //显示小数点后有几位数
	   String s=sc.nextLine();
	   Stack<Character> st1=new Stack<>(); //符号栈
	   Stack<Double> st2=new Stack<>(); //运算栈
	   for(int i=0;i<s.length();i++)
	   {
		   char x=s.charAt(i);
		   if(x>='0'&&x<='9')  //数字
		   {
			  sum=sum*10+x-'0';
			  get=true;
		   }
		   else if(x=='.')
		   {
			   tag=true;
			   continue;
		   }
		   else if(x=='(')
		   {
			   st1.push(x); //左括号直接入栈
			   flag++;  //数量加一
		   }
		   else if(x==')')
		   {
			   if(flag==0)  //如果没有左括号就不合法
			   {
				   System.out.print("2表达式不合法");
				   System.exit(-1);
			   }
			   //符号栈里肯定有左括号
			   if(get==true)  //获取到一个数字
				   {
				      if(tag==true)  //这个数是小数
				      {
				    	  sum=(double)sum/r;
				      }
				      st2.push(sum);
				   
				   }
			   r=1;
			   tag=false;
			   get=false;
			   sum=0;
			   while(!st1.empty()&&st1.peek()!='(') //将左括号和右括号之间的运算数计算完
			   {
				   calculate(st1.pop(),st2);
			   }
			   st1.pop(); //弹出左括号
			   flag--; //左括号数剪一
		   }
		   else if(compare(x)!=-1)  //符号
		   {
			   if(get==true) 
			   {
			      if(tag==true)
			      {
			    	  sum=(double)sum/r; //小数的话需要移动小数点
			      }
			      st2.push(sum);
			   }
		       r=1;
		      tag=false;//读取一个数,如果是小数则取消小数的标志位
		      get=false;
			   sum=0;  //更新数
			   if(st1.empty())  //符号栈为空,直接压进栈
			   {
				   st1.push(x);
			   }
			   else   //如果新加进的符号优先级比栈顶符号优先级低或相等,可以先进行计算
			   {
				   while(!st1.empty()&&compare(x,st1.peek()))
				   {
					   calculate(st1.pop(),st2);
				   }
				   st1.push(x);
			   }
		   }
		   else  //其他不合法的符号
		   {
			   System.out.print("3表达式不合法");
			   System.exit(-1);
		   }
		   if(tag==true)  //计算小数点后几位数
		   {
			   r*=10;
		   }
	   }
	   if(get==true) 
	   {
	      if(tag==true)
	      {
	    	  sum=(double)sum/r; //小数的话需要移动小数点
	      }
	      st2.push(sum);
	   }
       r=1;
      tag=false;//读取一个数,如果是小数则取消小数的标志位
      get=false;
	   sum=0;  //更新数
	   while(!st1.empty()&&!st2.empty())
	   {
		   calculate(st1.pop(),st2);
	   }
	  if(st1.empty()&&st2.size()==1) System.out.println("运算结果是"+String.format("%.4f", st2.pop()));
	  else System.out.println("4表达式不合法");
	}

5.如何判断该表达式是否合法
表达式如果不合法,主要体现在如下几个方面:
1.检验左右括号不匹配问题
设置变量flag记录左括号的个数,每当读取一个右括号的时候,左括号的个数都会减一,到最终检验左右括号是否存在多余.
右括号比左括号多的情况:

 int flag=0; //检测左括号的数量
.
.
.
.  else if(x=='(')
		   {
			   st1.push(x); //左括号直接入栈
			   flag++;  //数量加一
		   }
		   else if(x==')')
		   {
			   if(flag==0)  //如果没有左括号就不合法
			   {
				   System.out.print("2表达式不合法");
				   System.exit(-1);
			   }
			   //符号栈里肯定有左括号
........


          flag--; //左括号数减一


左括号比右括号多的情况:

 public static void calculate(char x,Stack<Double> st2)//根据x运算符对运算栈的数进行运算
	 {
		 if(st2.size()<2)
		 {
			 System.out.print("1表达式不合法/"); //会在这里报错
			 System.exit(-1);
		 }
		 ..
.
.
.
.
.}

2.出现其他非运算符

 for(int i=0;i<s.length();i++)
	   {
		   char x=s.charAt(i);
		   if(x>='0'&&x<='9')  //数字
		   {
			 
		   }
		   else if(x=='.')
		   {
			  
		   }
		   else if(x=='(')
		   {
			   
		   }
		   else if(x==')')
		   {
			 
		   }
		   else if(compare(x)!=-1)  //符号
		   {
			  
		   }
		   else  //其他不合法的符号
		   {
			   System.out.print("3表达式不合法");
			   System.exit(-1);
		   }

int compare(char x)函数会返回-1

3.运算中时的除数为0,指数为小数或者进行取余运算时出现小数
判断该数是否为小数:

 public static boolean is_(double x)  //判断该数是否有小数位
	 {
		 if(x%1==0)  //为整数
		 {
			 return false;
		 }
		 else return true;
	 }

在s2中取出运算数时进行检验

public static void calculate(char x,Stack<Double> st2)//根据x运算符对运算栈的数进行运算
	 {
		 if(st2.size()<2)
		 {
			
		 }
		 else
		 {
			 double a=st2.pop();
			 double b=st2.pop();
			 switch(x)
			 {
			 
			 case '/': 
				        if(a==0)
				       {
				    	   System.out.print("表达式不合法,因为除数不能为0");
						   System.exit(-1);
				       }
				       st2.push(b/a);
			 		   break;
			 case '%': 
				   if(is_(a)||is_(b))
		           {
		        	   System.out.print("%运算不能有小数参与!");
		        	   System.exit(-1);
		           }
				       st2.push(b%a);
			           break;
			 case '^': 
				   if(is_(a))
				   {
					   System.out.println("指数不能为小数");
					   System.exit(-1);
				   }
				   st2.push((double)Math.pow(b,a));
			           
			
			 }
			 
		 }
	 }

4.其他匹配问题
会在这个函数中检验出
一个运算符参与运算需要两个运算数,如果找不到两个运算数肯定是不合法的表达式

 public static void calculate(char x,Stack<Double> st2)//根据x运算符对运算栈的数进行运算
	 {
		 if(st2.size()<2)
		 {
			 System.out.print("1表达式不合法/");
			 System.exit(-1);
		 }
		 else
		 {
			 
		 }
	 }

体会

这道题的细节还是挺多的,像如何处理多位数,如何判断是否小数,如何检验表达式的合法性等,我觉得在编写这样的程序最好从顶向下设计,确定整个程序的大概框架,再不断填充细节,整个程序化成一个个小问题去解决,一个个小函数去检验正确性,这样查找问题比较容易. (函数的封装性挺有趣的)

不足

这个程序只能处理除了负数之外的各种加减乘除取余和幂运算,并能检验其正确性.如果出现了负数,则会发生错误.

扩展

根据中缀表达式求值也可通过转换成后缀表达式再进行求值
(只实现10以内的加减乘除)

public class Sta {
    public static int calculate(int x,int y,char c)
    {
        switch(c)
        {
            case '+': return x+y;
            case '-': return y-x;
            case '*': return x*y;
            case '/':  if(x==0)
            {
                System.out.println("除数不能为0,表达式不合理");
                System.exit(-1);
            }
            return y/x;
        }
        return -1;
    }
    public static int transform(String str)
    {
        Stack<Integer> s=new Stack<>();
        for(int i=0;i<str.length();i++)
        {
            char c=str.charAt(i);
            if(Isdigit(c))
            {
                s.push(c-'0');
            }
            else
            {
                if(s.size()<2)
                {
                    System.out.println("3表达式不合理");
                    System.exit(-1);
                }
                int a=s.pop();
                int b=s.pop();
               int sum=calculate(a,b,c);
               s.push(sum);
            }
        }
        if(s.size()==1) return s.pop();
        return -1;
    }
    public static int compare(char c) //比较符号的优先级
    {
        switch(c)
        {
            case '+': return 0;
            case '-': return 0;
            case '*': return 1;
            case '/': return 1;
        }
        return -1;
    }
    public static boolean Isdigit(char x)  //判读是不是数字型
    {
        if(x>='0'&&x<='9') return true;
        else return false;
    }
    public static boolean com(char a,char b) //判断符号a是否压入栈
    {
        if(b=='(')  //如果栈顶是左括号,无论什么情况a都压入栈
        {
            return true;
        }
        if(compare(a)>compare(b))
        {
            return true;
        }
        return false;
    }
    public static void main(String[] args) {
        Scanner sc=new Scanner(System.in);
        Stack<Character>s=new Stack<>();//运算符栈
        String str=sc.nextLine();
        StringBuffer sb=new StringBuffer(); //可变字符串
        int count=0;//左括号数量
        for(int i=0;i<str.length();i++)
        {
            char c=str.charAt(i);
            if(Isdigit(c)) sb=sb.append(c);//是数字的话就直接添加
            else    //非数字
            {
                     if(c=='(')
                    {
                        count++;
                        s.push(c); //左括号直接压栈
                    }
                 else if(c==')')
                    {
                        if(count==0) //没有左括号,但却有右括号,肯定不合法
                        {
                            System.out.println("1表达式不合法");
                            System.exit(-1);
                        }
                        while(!s.isEmpty()&&s.peek()!='(')//将左右括号之间的符号收进字符串中
                        {
                            sb=sb.append(s.pop());
                        }
                            s.pop();//将左括号弹出
                            count--;
                    }
                 //合法表达式
                    else if(compare(c)!=-1)//将栈顶符号和当前符号作比较
                    {
                        if(s.isEmpty())  s.push(c);
                        else
                        {
                            char x=s.peek();
                            if(com(c,x)) s.push(c);
                            else
                            {
                                while(!s.isEmpty()&&!com(c,x))
                                {
                                    x=s.pop();
                                    sb=sb.append(x);
                                }
                                s.push(c);
                            }
                        }
                }
                    else
                     {
                         System.out.println("2表达式不合法");
                         System.exit(-1);
                     }

            }
        }
        while(!s.isEmpty())
        {
            sb=sb.append(s.pop());
        }
        System.out.println(transform(sb.toString()));
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第1章 绪论 1.1 数据结构的基本概念和术语 1.1.1 引言 1.1.2 数据结构有关概念及术语 1.1.3 数据结构和抽象数据类型(ADT) 1.2 算法描述与分析 1.2.1 什么是算法 1.2.2 算法描述工具——C语言 1.2.3 算法分析技术初步 习题一 第2章 线性表 2.1 线性表的定义及其运算 2.1.1 线性表的定义 2.1.2 各种运算简介 2.2 线性表的顺序存储结构(向量) 2.2.1 顺序存储结构(向量) 2.2.2 向量中基本运算的实现 2.3 线性表的链表存储结构 2.3.1 单链表与指针 2.3.2 单链表的基本运算 2.4 循环链表和双向链表 2.4.1 循环链表 2.4.2 双向链表 2.4.3 顺序存储结构与链表存储结构的综合分析与比较 2.5 多项式相加问题 2.5.1 多项式相加的链表存储结构 2.5.2 多项式相加的算法实现 2.6 线性表的算法实现举例 2.6.1 实现线性表顺序存储结构及运算的C语言源程序 2.6.2 单链表处理的C语言源程序 习题二 第3章 栈和队列 3.1 栈 3.1.1 栈的定义及其运算 3.1.2 栈的顺序存储结构(向量) 3.1.3 栈的链表存储结构 3.1.4 栈的应用 3.2 队列 3.2.1 队列的定义及运算 3.2.2 队列的顺序存储结构(向量) 3.2.3 队列的链表存储结构 3.3 栈和队列的算法实现举例 习题三 第4章 串 4.1 串的基本概念 4.2 串的存储结构 4.2.1 串的顺序存储 4.2.2 串的链表存储 4.2.3 串变量的存储映象 4.3 串的运算 4.3.1 串的运算简介 4.3.2 串的匹配运算 4.4 文本编辑 习题四 第5章 数组和广义表 5.1 数组的基本概念 5.1.1 数组的概念 5.1.2 数组的顺序表示 5.1.3 特殊矩阵的压缩存储 5.2 稀疏矩阵的三元组存储 5.2.1 三元组表 5.2.2 稀疏矩阵的运算 5.3 稀疏矩阵的十字链表存储 5.3.1 十字链表的组成 5.3.2 十字链表的有关算法 5.4 广义表 5.4.1 广义表的概念和特性 5.4.2 广义表的存储结构 5.4.3 求广义表的深度 5.4.4 广义表的输出 5.4.5 建立广义表的存储结构 5.5 迷宫问题 习题五 第6章 树 6.1 树的基本概念和术语 6.1.1 树的定义 6.1.2 树的常用术语 6.1.3 树的表示方法 6.2 二叉树 6.2.1 二叉树的定义 6.2.2 二叉树的重要性质 6.2.3 二叉树的存储结构 6.2.4 二叉树二叉链表的一个生成算法 6.3 遍历二叉树 6.3.1 先根遍历 6.3.2 中根遍历 6.3.3 后根遍历 6.3.4 二叉树遍历算法的应用 6.4 线索二叉树 6.4.1 线索二叉树的基本概念 6.4.2 线索二叉树的逻辑表示图 6.4.3 中根次序线索化算法 6.4.4 在中根线索树上检索某结点的前趋或后继 6.4.5 在中根线索树上遍历二叉树 6.5 二叉树、 树和森林 6.5.1 树的存储结构 6.5.2 树与二叉树之间的转换 6.5.3 森林与二叉树的转换 6.5.4 一般树或森林的遍历 6.6 树的应用 6.6.1 二叉排序树 6.6.2 哈夫曼树及其应用 6.7 二叉树的建立和遍历C语言源程序示例 习题六 第7章 图 7.1 图的基本概念和术语 7.1.1 图的基本概念 7.1.2 路径和回路 7.1.3 连通图 7.1.4 顶点的度 7.2 图的存储结构 7.2.1 邻接矩阵 7.2.2 邻接链表 7.3 图的遍历和求图的连通分量 7.3.1 图的建立 7.3.2 图的遍历 7.3.3 求图的连通分量 7.4 图的生成树 7.4.1 生成树的概念 7.4.2 最小生成树 7.4.3 普里姆(Prim)算法和克鲁斯卡尔(Kruskal)算法 7.5 最短路径 7.5.1 单源顶点最短路径问题求解 7.5.2 求有向网中每对顶点间的路径 7.6 有向无环图及应用 7.6.1 拓扑排序 7.6.2 关键路径 7.7 图的算法C语言程序实现举例 7.7.1 无向图的邻接表的建立和遍历 7.7.2 有向无环图的拓扑排序和求关键路径 习题七 第8章 查找 8.1 基本概念
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值