二叉树解决四则运算的研究

背景:

  读大学那会上了计算机结构及算法,工作了几年,里面的内容在实际开发中很少运用,可能是自己所在的行业限制,让很多大学的知识还没有发挥的空间,感觉现在主要的就是实现业务逻辑,初中知识足够了,简单的逻辑。最近拿起书本又捡起了数据结构和算法,结合jdk的源码,感慨颇深,感觉自己这几年技术积累太少,或者说只注重了广度,深度上远远不够,这对自己以后的职业生涯是大大不利的。
  行,回到正题,大学那会学了树形结构,最长用的就是二叉树。其实这几年在项目中也有运用,就是存在这种父子机构的数据就直接可以存成树形结构,这样再设计一张表存储节点信息,当然也是通过主外键来实现每条记录之间父子关系。不过树里面的先序遍历,中序遍历和后序遍历基本上就没用过。最近遇到一个问题就很有意思,就是计算四则表达式。
  例如如果要用计算机来计算:(1+3)*12-3,这不是小学计算题吗?计算机来算不是小case吗?我开始也是这么想的,可真写起来代码来难度就大了,后来仔细思考下,觉得我们直觉上简单的,用计算机不一定简单,数学上能简单解决的,或许用代码来实现就没那么容易。
 现在来思考计算机最容易实现什么,就是你告诉他两个数,然后再告诉它需要执行什么运算,他就可以很快算出来,而算数表达式是通过括号﹑运算优先级﹑书写顺序来进行运算的。
 所以现在为了解决书写的计算式到计算机能理解的运算式的转换,需要借助二叉树结构。

探究:

基本的树的结构就这样:

父节点
左儿子
右儿子

考虑如果将父节点存成运算符,两个子节点存成数据,这样让计算机先读取两个子节点数据,再读取父节点运算符就可以执行运算了。

+
1
3

那对于复杂一点的算式呢?
如:2*3+1

+
*
1
2
3

先算左边叶子节点2*3得到结果6,然后6+1=7,可以得到结果。可以看到运算的数都在叶子节点上,运算符出现在非叶子节点上。而且按照上面的执行顺序刚好满足了二叉树的后序遍历,从根节点开始先访问左节点,再访问右节点,最后访问父节点,如此递归的进行。
为了简化开始的过程,我们先不考虑有括号的情况,后面再继续讨论加入括号的情况。经过规律探索可以根据算式来这样构建树:

  1. 先插入一个运算数,若树为空,那么这个节点作为根节点。
  2. 插入一个运算符*,那么这个节点作为上面的那个节点的父节点,而且上面那个节点成为它的左儿子。
  3. 再插入一个数字,那这个数字成为上面运算符的右儿子。
  4. 再插入一个运算符+,那这个运算符成为2中运算符的父节点.
  5. 再插入一个数字,这个数字成为2中节点的右儿子。

如果情况是这样的:
算式:2+3*1
树应该是这样:

+
2
*
3
1

规则变成这样:

  1. 先插入一个运算数,若树为空,那么这个节点作为根节点。
  2. 插入一个运算符+,那么这个节点作为上面的那个节点的父节点,而且上面那个节点成为它的左儿子。
  3. 再插入一个数字,那这个数字成为上面运算符的右儿子。
  4. 再插入一个运算符*,那这个运算符成为3中运算符的父节点.,3中节点成为该运算符的左儿子。
  5. 再插入一个数字,这个数字成为上面运算符的右儿子。

两种规则情况一整合,可以得出如下结论:

  1. 插入一个运算数,若树为空,那么这个节点作为根节点;若不为空,那么成为前一个运算符的右儿子。
  2. 插入运算符+,直接成为目前树的根节点,之前的根节点成为运算符的左子树。
  3. 插入运算符*,成为上一个操作数的父节点,操作数成为该运算符左儿子。
    这样3条规则就符合了上述两种树的构建,之后新增操作数和符号,规则也一致,同样符号-和+规则一致,符号*和/规则一致,可以验证只要属于同一优先级的运算符规则都一致。

构建:

现在就可以开始根据规则来构建我们的二叉树了,先定节点类,如下:

class Node{
		public String text;
		public Node(String text){
			this.text=text;
		}
		public Node leftNode,rightNode;
			
	}

再定义树类:

	class Tree{
		public Node root;
		
		public Tree(Node root){
			this.root=root;
		}
		public Tree(){}
		
//判断一个字符串是否是数字				
		private static boolean isNum(String s){
			char[] a=s.toCharArray();
			for(int i=0;i<a.length;i++){
				if(a[i]<'0'||a[i]>'9'){
					return false;
				}
			}
			return true;
		}
//加入元素方法,可以加入数字字符串,符号字符串,ch是加入的字符串变量,preNode是上一个节点		
		public  Node addElement(String ch,Node preNode){
			Node node=new Node(ch);
			if(isNum(ch)){
				if(root==null){					
					this.root=node;
				}else{					
					preNode.rightNode=node;										
				}	
			}else if(ch.equals("+")||ch.equals("-")){
				
				node.leftNode=this.root;
				this.root=node;
			}else if(ch.equals("*")||ch.equals("/")){
//这里是成为上一个节点的父节点	的操作,由于子节点还没有提供方法指向父节点,所以这里采用了调换节点中存储的text方式来实现		
				node.text=preNode.text;
				preNode.text=ch;
				preNode.leftNode=node;
				return preNode;
				
			}
			
		   return node;
		}
		
//递归的方式进行计算,和树的后序遍历方法一致		
		public int count(Node node){					
				if("+".equals(node.text)){
					return count(node.leftNode)+count(node.rightNode);
					
				}else if("-".equals(node.text)){
					return count(node.leftNode)-count(node.rightNode);
					
				}else if("*".equals(node.text)){
					return count(node.leftNode)*count(node.rightNode);
					
				}else if("/".equals(node.text)){
					return count(node.leftNode)/count(node.rightNode);				
				}else{
					
					return Integer.parseInt(node.text);
				}
		
		}
//静态方法,读取算式的文本信息,逐个元素加入到树中,最后根据构建好的树进行四则运算		
		public static int countZ(String exp){
			Tree tree=new Tree();				
			
			Node pre=null;			
			int i=1;			
			StringBuilder numS=new StringBuilder();
			//flag用于标记截取的字符串是否是数字
			int flag=0;
			while(i<=exp.length()){
				String s=exp.substring(i-1,i);
				if(isNum(s)){
				//判断截取字符是数字需要进一步判断下一个字符
					numS.append(s);
					flag=1;
					if(i==exp.length()){
						tree.addElement(numS.toString(),pre);																														
					}
				}else{
				    //上一个字符是数字,此字符是运算符
					if(flag==1){						
						Node node=tree.addElement(numS.toString(),pre);						
						Node nodeC=tree.addElement(s,node);
						pre=nodeC;
						flag=0;
						numS.delete(0,numS.length());
					}else{
					//上一个字符不是数字,直接加入树中
						Node nodeC=tree.addElement(s,pre);
						pre=nodeC;
					}
					
				}
				i++;
			}
			return tree.count(tree.root);		
		}
		
		
		
		
	}

调用上面代码计算不带括号的运算式:

public static void main(String[] args) {
		
		/*
		Scanner in=new Scanner(System.in);
		while(in.hasNext()){	
		   String exp=in.nextLine();
		   exp=ToExp(exp);
		   System.out.println(Count(exp));
		}
		*/
		
		String exp="3+2*4";
		int result=Count(exp);
		System.out.println(result);
		
          
	}

控制台输出结果:11,正确。

更进一步:

如果加入括号运算,结果会怎样呢?
思考,加入括号相当于将括号内的分割成了子算式,算出子算式就可以去括号了,例如:
(1+2)3+2
这个可以先算1+2=3,将3带入,且可以去掉括号得:
3
3+2,又转换为了无括号的运算。
从上面例子可以看出,我们可以一层一层去掉括号,然后把括号内的运算结果直接体现在括号里,并且去掉括号,这样就可以了。如果有多层括号那就依次方法,一层一层去,一直到没有括号的运算位置。看到没,刚好是个递归。代码实现如下:

public static int Count(String exp){
         //括号起点
		int start=exp.indexOf("(");
		//与上面"("对应的")"位置
		int end=GetEnd(exp);
		//没有括号后跳出
		if(start<0){
			return Tree.countZ(exp);
		}
		//截取到子字符串表达式
		String subExp=exp.substring(start+1,end);
		//将子字符串表达式运算结果替换原来的子表达式,且去括号
		String newExp=exp.substring(0,start)+Count(subExp)+exp.substring(end+1);
		return Count(newExp);
	}

这里需要注意的是GetEnd()函数,怎么判定一个”)“与第一个出现的”(“对应呢?需要遵从下面规则:

  1. 在第一个”(“之后
  2. 在第一次出现了”)“之后
  3. 在2出现之后的”(“之前
  4. 在2和3之间的最后一个”)“
    举个例子:
    ((1+2)+3)+(3+4)
    判定第一个”(“对应的”)“,用上述规则就可以找到,现实现代码如下:
	public static int GetEnd(String exp){
		int i=0;
		int j=exp.length();
		while(i<exp.length()-1){
		    //出现第一个")"
			if(exp.substring(i,i+1).equals(")")){
				j=i;
			}
			if(i>j){
			    //再出现”)“之后第一次出现”(“
				if(exp.substring(i,i+1).equals("(")){
					break;
				}
			}
			i++;			
		}
		i++;
		//截取字符串,找到此时最后一个”)“
		int end=exp.substring(0,i).lastIndexOf(")");
		
		return end;	
	}


OK搞定,现在随意输入个带小括号的表达式看看:

public static void main(String[] args) {
		
		/*
		Scanner in=new Scanner(System.in);
		while(in.hasNext()){	
		   String exp=in.nextLine();
		   exp=ToExp(exp);
		   System.out.println(Count(exp));
		}
		*/
		
		String exp="((3+2)*4+12)-(1+5)";
		int result=Count(exp);
		System.out.println(result);	     
	}

控制台显示:
26
正确。

现在还有个小问题,如果出现了中括号,大括号则呢么办?简单,直接替换为小括号就行,括号起的作用都是一样的。

public static String ToExp(String exp){
		
		return exp.replace("[", "(").replace("{", "(").replace("]", ")").replace("}",")");
	}

结束语:

以上是通过二叉树来实现四则运算的整个过程,当然还存在许多不足,比如还需要判定一个表达式是否合法,比如除法运算如果除不尽该如何处理,怎么计算带有小数的,怎么插入更多的运算符等。这个还需要进一步探索,上面方法有不对的地方还请各位技术达人指正。
下一篇博客想实现自动生成给定复杂程度的四则运算,然后让计算机来运算,这样做的目的是直接出一些题目来给小朋友算,然后电脑来给定答案。进一步可能结合图像识别算法,可以拍照四则运算表达式,然后计算批改作业。

  • 2
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值