随机生成四则运算式及答案(含括号)

【原创,转载请注明出处!】

记得我大二时候java课程的学期大作业有这样一题:

根据用户的选项,随机生成n道四则运算式,让用户回答,然后检验答案是否正确。用java的图形界面实现,也就是swing。

当时很困惑随机生成四则运算式该如何实现,如果按当时的思路的话,估计也就是一个数字一个符号这样one by one接下去。

上个星期有些空,然后突然想起了这个题目,然后在群里讨论了下,发现用二叉树来实现比较直观。

随机生成四则运算式及答案(含括号)_第1张图片


对于这样一棵二叉树,每个叶子节点都是数字,每个父节点都是符号,然后中序遍历结果就是我们所需要的四则运算,而且每次父节点返回的时候,可以根据符号进行运算式的计算,中序遍历的结果:

1+2-3*4

但是如何产生括号呢?

有一个简单的办法就是父节点每次返回的时候带上括号,于是运算式就变成了这样:

((1+2)-(3*4))

但是新的问题又来了,对于某些式子,比如这里1+2和3*4的括号是多余的,加上括号并不会改变运算式的计算顺序,对于这样的括号,应该要去掉。

一开始我想的太简单了,'*'、'/'的优先级是高于'+'、'-'的,那么如果运算符是'*'、'/'的话,直接把括号去掉就行了。结果当然是错的,因为没有把括号的优先级考虑进来,

正确的优先级应该是这样的:'('、')'>'*'、'/'>'+'、'-'

通过整理,我们可以得到这样的规律:

假设待去括号的表达式为 (m1 op1 n1) op (m2 op2 n2) 这里m1、n1、m2、m2可能自身就是个表达式,也可能是数字,op、op1、op2为运算符

1、若op为'/',则 (m2 op2 n2)的括号必须保留;

2、若op为'*'或'-',如果op2为'+'或'-',则(m2 op2 n2)的括号必须保留;

3、若op为'*'或'/',如果op1为'+'或'-',则(m1 op1 n1)的括号必须保留;

4、 除此之外,去掉括号不影响表达式的计算顺序。

                                                                                                           【这里整理的去括号法则来源于互联网】

至此,我们就能得到一个比较随机的四则运算式。

这里为什么又是比较随机呢?

因为还有一些细节我们尚未考虑,比如除数为0,这里的除数为0有两种情况:

1、随机到的数字为0

2、除号后面的表达式的计算结果为0

对于情况1我们可以把这个数字再Random一下,但是对于情况2我们也要把表达式重新生成一遍吗?

一种比较偷懒也是比较简单的办法是,把除号随机成其他三种符号,这样就能避免除数为0的情况。

但是这样做也有不好的地方,就是符号的随机性被破坏了,也就是说除号出现的可能性降低了。

每个方法有利有弊,可以自己把握。

以上是我及我的小伙伴们的思路,仅供参考,或许有更好的生成方法,可以相互探讨。

下面是code time

BinaryTree.java

/**
 * 
 * @author kuku713
 * @version 1.0
 * 
 */

import java.util.ArrayList;


public class BinaryTree 
{
	
	private TreeNode root;
	private int num;
	private ArrayList opeList = new ArrayList();
	
	public BinaryTree(int num){
		this.num = num;
	}
	
	public int getNum(){
		return num;
	}
	
	public void setNum(int num){
		this.num = num;
	}
	
	public void setTreeNode(TreeNode root){
		this.root = root;
	}
	
	
	/**
	 * 获取最终的表达式,必须在CalAndVal()方法后调用
	 * 
	 * @return str
	 */
	public String toString(){
		String str = root.toString();
		str = str.substring(1, str.length()-1);
		return str;
	}
	
	/**
	 * 计算并验证表达式
	 * 
	 * @return result
	 */
	public String CalAndVal(){
		return root.getResult();
	}
	
	/**
	 * 计算二叉树的深度(层数) 
	 * 
	 * @return deep
	 */
	public int getDeep(){
		int i = this.num;
		int deep = 2;
		while(i/2 > 0){
			deep++;
			i /= 2;
		}
		return deep;
	}
	
	/**
	 * 生成二叉树
	 * 
	 */
	public void createBTree(){
		TreeNode lchild, rchild, lnode, rnode;
		
		if(num == 1){
			lchild = new TreeNode(String.valueOf(Ran.getNumber(10)), null, null);
			rchild = new TreeNode(String.valueOf(Ran.getNumber(10)), null, null);
			root = new TreeNode(String.valueOf(Ran.getOperator()), lchild, rchild);
		}
		else{
			int num1 = 0;
			int n = getDeep() - 3;
			boolean[] place = Ran.getChildPlace(num);
			root = new TreeNode(String.valueOf(Ran.getOperator()), null, null);
			opeList.add(root);
			
			for(int i = 0; i < n; i++){
				for(int j = 0; j < (int)Math.pow(2, i); j++, num1++){
					lchild = new TreeNode(String.valueOf(Ran.getOperator()), null, null);
					rchild = new TreeNode(String.valueOf(Ran.getOperator()), null, null);
					opeList.get(j + num1).setChild(lchild, rchild);
					opeList.add(lchild);
					opeList.add(rchild);
				}
			}
			
			for(int i = 0; i < place.length; i++){
				if(place[i]){
					lnode  = new TreeNode(String.valueOf(Ran.getNumber(10)), null, null);
					rnode  = new TreeNode(String.valueOf(Ran.getNumber(10)), null, null);
					if(i%2 == 0){
						lchild = new TreeNode(String.valueOf(Ran.getOperator()), lnode, rnode);
						opeList.add(lchild);
						opeList.get(num1).setLchild(lchild);
					}
					else{
						rchild = new TreeNode(String.valueOf(Ran.getOperator()), lnode, rnode);
						opeList.add(rchild);
						opeList.get(num1).setRchild(rchild);
					}
				}
				else{
					if(i%2 == 0){
						lchild = new TreeNode(String.valueOf(Ran.getNumber(10)), null, null);
						opeList.get(num1).setLchild(lchild);
					}
					else{
						
						rchild = new TreeNode(String.valueOf(Ran.getNumber(10)), null, null);
						opeList.get(num1).setRchild(rchild);
					}
				}
				num1 = num1 + i%2;
			}
		}
	}
}

Ran.java

/**
 * 
 * @author kuku713
 * @version 1.0
 * 
 */

import java.util.Random;


public class Ran {
	
	/**
	 * 获取随机的符号
	 * 
	 * @return operator
	 */
	
	public static char getOperator(){
		char operator = 0;
		Random ran = new Random();
		int i = ran.nextInt(4);
		switch(i){
			case 0:
				operator = '+';
				break;
			case 1:
				operator = '-';
				break;
			case 2:
				operator = '*';
				break;
			case 3:
				operator = '/';
				break;
		}
		return operator;
	}
	
	
	/**
	 * 根据输入的范围获取随机数
	 * 
	 * @param max
	 * @return number
	 */
	
	public static int getNumber(int max){
		int number = 0;
		Random ran = new Random();
		number = ran.nextInt(max+1);
		return number;
	}
	
	/**
	 * 根据运算符的个数随机产生子节点的位置
	 * 
	 * @param num
	 * @return childPlace
	 */
	
	public static boolean[] getChildPlace(int num){
		int d = 0;
		int size = 0, j=1;
		while(num >= (int)Math.pow(2, j)){
			j++;
		}
		d = (int)Math.pow(2, j) - 1 - num;
		size = (int)Math.pow(2, j-1);
		boolean[] k = new boolean[size];
		for(int i = 0; i < size; i++){
			k[i] = true;
		}
		for(int i = 0; i < d; i++){
			Random ran = new Random();
			int f = ran.nextInt(size);
			while(k[f] == false)
			{
				f = ran.nextInt(size);
			}
			k[f] = false;
		}
		return k;
	}
}

TreeNode.java

/**
 * 
 * @author kuku713
 * @version 1.0
 * 
 */

public class TreeNode {
	
	private String str;
	private TreeNode rchild = null;
	private TreeNode lchild = null;
	
	public TreeNode(String str){
		this.str = str;
	}
	
	public TreeNode(String str, TreeNode lchild, TreeNode rchild){
		this.str = str;
		this.rchild = rchild;
		this.lchild = lchild;
	}
	
	public void setChild(TreeNode lchild, TreeNode rchild){
		this.lchild = lchild;
		this.rchild = rchild;
	}
	
	public TreeNode getRchild() {  
        return rchild;  
    }  
    public void setRchild(TreeNode rchild) {  
        this.rchild = rchild;  
    }  
    public TreeNode getLchild() {  
        return lchild;  
    }  
    public void setLchild(TreeNode lchild) {  
        this.lchild = lchild;  
    }
	
	public String getStr(){
		return str;
	}
	
	/**
	 * 获取每个节点的运算结果,并检验除法
	 * 1)除数为0
	 * 2)不能整除
	 * 出现以上两种情况的话将该运算符转换成其他三种运算符
	 * 
	 * @return result
	 */
	public String getResult(){
		if(hasChild()){
			switch(str){
				case "+":
					return String.valueOf(Integer.parseInt(getLchild().getResult()) + Integer.parseInt(getRchild().getResult()));
				case "-":
					return String.valueOf(Integer.parseInt(getLchild().getResult()) - Integer.parseInt(getRchild().getResult()));
				case "*":
					return String.valueOf(Integer.parseInt(getLchild().getResult()) * Integer.parseInt(getRchild().getResult()));
				case "/":
					if(getRchild().getResult().equals("0")){
						while(str.equals("/")){
							str = String.valueOf(Ran.getOperator());
						}
						return this.getResult();
					}
					else if(Integer.parseInt(getLchild().getResult()) % Integer.parseInt(getRchild().getResult()) != 0){
						while(str.equals("/")){
							str = String.valueOf(Ran.getOperator());
						}
						return this.getResult();
					}
					else
						return String.valueOf(Integer.parseInt(getLchild().getResult()) / Integer.parseInt(getRchild().getResult()));
			}
		}
		return str;
	}     
    
    /**
     * 先对每个运算式添加括号,然后根据去括号法则,去掉多余的子式的括号
     * 
     * @return string
     */
    public String toString(){
    	String Lstr = "", Rstr = "", Str = "";
    	if(hasChild()){
    		//右子树如果有孩子,说明右子树是一个表达式,而不是数字节点。
    		if(getRchild().hasChild()){                         
    			//判断左邻括号的运算符是否为'/'
    			if(str.equals("/")){
    				//获取右子树的表达式,保留括号
    				Rstr = getRchild().toString();              
    			}
    			//判断左邻括号的运算符是否为'*'或'-'
    			else if(str.equals("*") || str.equals("-")){
    				//判断op是否为'+'或'-'
    				if(getRchild().str.equals("+") || getRchild().str.equals("-")){	
    					Rstr = getRchild().toString();			
    				}
    				else{
    					//获取右子树的表达式,并且去括号 
    					Rstr = getRchild().toString().substring(1, getRchild().toString().length()-1);	
    				}
    			}
    			else{
    				//右子树除此之外都是可以去括号的。
    				Rstr = getRchild().toString().substring(1, getRchild().toString().length()-1);		
    			}
    		}
    		else{
    			Rstr = getRchild().str;
    		}
    		//左子树的情况同右子树类似
    		if(getLchild().hasChild()){												
    			if(str.equals("*") || str.equals("/")){
    				if(getLchild().str.equals("+") || getLchild().str.equals("-")){
    					Lstr = getLchild().toString();
    				}
    				else{
    					Lstr = getLchild().toString().substring(1, getLchild().toString().length()-1);
    				}
    			}
    			else{
    				Lstr = getLchild().toString().substring(1, getLchild().toString().length()-1);
    			}
    		}
    		else{
    			Lstr = getLchild().str;
    		}
    		//获取当前的运算式,并加上括号
    		Str = "(" + Lstr + str + Rstr + ")"; 									
    	}
    	else{
    		//若没有孩子,说明是数字节点,直接返回数字
    		Str = str;
    	}
    	return Str;
    }
    
    public boolean hasChild(){
    	if(lchild == null && rchild == null)
    		return false;
    	else
    		return true;
    }
}

Test.java

/**
 * 
 * @author kuku713
 * @version 1.0
 *
 */

public class Test {
	
	public static void main(String args[]){
		BinaryTree bTree;
		for(int i = 0; i < 6; i++){
			bTree = new BinaryTree(2);
			bTree.createBTree();
			String result = bTree.CalAndVal();
			System.out.println(bTree.toString() + "=" + result);
		}
	}
}



你可能感兴趣的:(java)