24点算法讲解与实现

题目描述:在52张扑克牌中(去掉大小王),随机抽取4张牌,找到所有可能的情况和解。

前言
       博主曾在网上看到有很多关于24点的算法,但很多都是没有找全所有表达式,要么就是没有去重,而且搜索的时间过长,有些慢的要半个小时才能得到结果 难过。所以经过我的不懈努力 奋斗,经过几天的研究,目前我的这个24点的算法能够高效的找出所有解的情况 微笑。经过测试,平均在0.1几秒左右就可以找到所有情况。


算法分析
       本算法采用穷举的思路,对所有数字和操作符进行组合,从而找到所有的情况。
首先想想,如果是人来计算24点,应该怎么计算?比如1,2,3,4.  首先我们可能会思考一下,然后得出结果(1+2+3)*4=24。
对,没错,那么现在对上面思考的过程进行仔细分析一下,24是怎么来的?6*4=24,对吧,那么6是怎么来的?6=1+2+3。
现在有个问题,虽然我们一眼就能看出1+2+3=6,但是这个过程还是经过了2步计算,首先我们计算1+2=3,然后在计算3+3=6,然后再计算6*4=24,对吧。也就是说我们会从这4个数中选择2个数来进行计算,然后得到一个结果,在将这个结果与剩下的2个数中选择一个数来计算,简单来说就是就是每次我们只会计算2个数,当然这2个数可能有好几种不同的运算。
下面我们再来看看13,13,13,12这几个数,咋一看这个好像没有上面的数字好算了,不能一下得出结果,这里给出3种解(当然不止3种):(13+13)/(13/12) 和 13-((13/13)-12) 和 (13+12)-(13/13),我们再来想想,每次只运算2个数,直到得出结果。
       OK,明白了上面,我们再来分析下计算机是怎么运算的。其实也是同样的思路,每次只运算2个数,然后将结果拿去进行下一次运算。那么现在我们有4个数,如1,2,3,4. 在这4个数位置不变的情况下, 第一次选择2个数来计算,有哪些情况?如果位置不变,那只有3种情况:(1-2)-3-4, 1-(2-3)-4, 1-2-(3-4)(先假设操作符都是-号)。那第2次计算呢,有下面几种情况,如果第1次是(1-2)-3-4,则第2次可能是((1-2))-3-4和(1-2)-(3-4),如果第1次是1-(2-3)-4,则第2次可能是(1-(2-3))-4和1-(2-(3-4)),如果第1次是1-2-(3-4),则第2次可能是(1-2)-(3-4)和1-(2-(3-4)),
然后第3次运算就只剩2个数了,在计算这2个数的结果是不是24就行了。
       上面我们假设的是操作符都是-号,但实际情况可能有很多种,现在我们还是在这4个数位置不变的情况下,再来改变操作符,即每次2个数进行运算的时候,有4种情况,即1-2,  1+2,  1*2,  1/2,(在这2个数位置不变的情况下)。那么下次进行计算的时候呢?同样有4种情况(+-*/),最后一次计算(第3次)同理。这样我们就找到了在这4个数位置不变的情况下的所有解的情况。
        那么接下来再考虑这4个数位置变化的情况,即1,2,3,4  可以变成4,3,2,1   和1,4,2,3等。同理,当位置变化时,我们按照上面的方法重新计算。这样就可以找出每一种情况啦。这里用的是排列组合,如1,2,3,4有24种不同的排列。


以下是具体代码:
public class Expression {
	//用来判断是否有解
	private static boolean flag = false;
	//存放操作符
	private static char[] operator = { '+', '-', '*', '/' };
	//存放所有结果
	private static ArrayList<String> results = new ArrayList<String>();
	
	public static void main(String[] args) {
		//所有正确的解的个数
		int rightCount = 0;
		//所有情况的个数
		int allCount = 0;
		//存放4个数字
		double[] number = new double[4];
		
		long startTime = System.currentTimeMillis();
		
	/*	第1次去重,过滤掉可能产生的重复的情况,比如1,2,3,4  和4,3,2,1
		因为后面是通过排列组合来找出所有情况,1,2,3,4可以组合成4,3,2,1
		这样就重复了,这里为了过滤掉这些重复的*/
		for (int i = 1; i <= 13; i++) {
			for (int j = i; j <= 13; j++) {
				for (int k = j; k <= 13; k++) {
					for (int m = k; m <= 13; m++) {
						number[0] = i;
						number[1] = j;
						number[2] = k;
						number[3] = m;
						//由于过滤掉重复的,这里重新计算重复的次数(在计算所有情况的个数时需要)
						//如果你不需要计算所有情况的个数,可以不需要
						int count = times(i, j , k ,m);
						allCount += count;
						duplicateRemoval(number);
						//判断是否有解
						if(flag == true){
							rightCount += count;
							flag = false;
						}
					}
				}
			}
		}
		long endTime = System.currentTimeMillis();
		
		for (int i = 0; i < results.size(); i++) {
			System.out.println(results.get(i));
		}
		System.out.println("共耗费时间:" + (endTime - startTime) + "ms");
		System.out.println("所有可能的个数:" + allCount);
		System.out.println("有解的个数:" + rightCount);
		System.out.println("有解的几率" + (double)rightCount/allCount);
	}
	
	/**
	 * 由于最开始过滤掉一部分重复的情况,但这些重复情况是存在的
	 * 这里是为了计算每种重复情况有多少次数,如当3张牌相同,另一张牌不同时,
	 * 如3,3,3,5  抽牌时有16种不同的情况(根据花色的不同)
	 * 而在计算时为了去重把这些过滤掉了,这里是为了重新计算这些情况
	 * 如果你不需要计算所有情况的个数,可以不需要次方法
	 */
    private static int times(int i,int j,int k,int m){
        //判断有多少种重复
        Set<Integer> set = new HashSet<Integer>();
        set.add(i);
        set.add(j);
        set.add(k);
        set.add(m);
        if(set.size() == 1){
        	//当4个数的数字全部一样时(不同花色),只可能有一种组合
            return 1;
        } else if(set.size() == 3){
        	//当4个数中,有两个数相同,其余的数都不相同时
            return 96;
        } else if(set.size() == 4){
        	//当4个数全部不同时
            return 256;
        } else{
            if((i == j && k == m)||(i == k && j == m)||(i == m && k == j)){
            	//当4个数中,两两相同时
                return 36;
            } else {
            	//当4个数中有三个数相同,另外一个数不同时
                return 16;
            }
        }
    }
    
         /**
	 * 第2次去重,由于排列组合可能导致数字组合的重复
	 * 这里进行第2次过滤,只计算给定4个数的所有不同的排列
	 */
	private static void duplicateRemoval(double[] number){
		Map<Double, Integer> map = new HashMap<Double, Integer>();
		//存放数字,用来判断输入的4个数字中有几个重复的,和重复的情况
		for (int i = 0; i < number.length; i++) {
			if(map.get(number[i]) == null){
				map.put(number[i], 1);
			} else {
				map.put(number[i], map.get(number[i]) + 1);
			}
		}
		if(map.size() == 1){
			//如果只有一种数字(4个不同花色的),此时只有一种排列组合,如6,6,6,6
			calculation(number[0], number[1],number[2],number[3]);
		} else if(map.size() == 2){
			//如果只有2种数字,有2种情况,如1,1,2,2和1,1,1,2
			int index = 0;//用于数据处理
			int state = 0;//判断是那种情况
			for (Double key : map.keySet()) {
				if(map.get(key) == 1){
					//如果是有1个数字和其他3个都不同,将number变为 number[0]=number[1]=number[2],
					//将不同的那个放到number[3],方便计算
					number[3] = key;
					state = 1;
				} else if(map.get(key) == 2){
					//两两相同的情况,将number变为number[0]=number[1],number[2]=number[3]的情况,方便计算
					number[index++] = key;
					number[index++] = key;
				} else {
					number[index++] = key;
				}
			}
			//列出2种情况的所有排列组合,并分别计算
			if(state == 1){
				calculation(number[3], number[1], number[1], number[1]);
				calculation(number[1], number[3], number[1], number[1]);
				calculation(number[1], number[1], number[3], number[1]);
				calculation(number[1], number[1], number[1], number[3]);
			}
			if(state == 0){
				calculation(number[1], number[1], number[3], number[3]);
				calculation(number[1], number[3], number[1], number[3]);
				calculation(number[1], number[3], number[3], number[1]);
				calculation(number[3], number[1], number[1], number[3]);
				calculation(number[3], number[3], number[1], number[1]);
				calculation(number[3], number[1], number[3], number[1]);
			}
		} else if(map.size() == 3){
			//有3种数字的情况
			int index = 0;
			for (Double key : map.keySet()) {
				if(map.get(key) == 2){
					//将相同的2个数字放到number[2]=number[3],方便计算
					number[2] = key;
					number[3] = key;
				} else {
					number[index++] = key;
				}
			}
			//排列组合,所有情况
			calculation(number[0], number[1], number[3], number[3]);
			calculation(number[0], number[3], number[1], number[3]);
			calculation(number[0], number[3], number[3], number[1]);
			calculation(number[1], number[0], number[3], number[3]);
			calculation(number[1], number[3], number[0], number[3]);
			calculation(number[1], number[3], number[3], number[0]);
			calculation(number[3], number[0], number[1], number[3]);
			calculation(number[3], number[0], number[3], number[1]);
			calculation(number[3], number[1], number[0], number[3]);
			calculation(number[3], number[1], number[3], number[0]);
			calculation(number[3], number[3], number[0], number[1]);
			calculation(number[3], number[3], number[1], number[0]);
		} else if(map.size() == 4){
			//4个数都不同的情况
			getNumber(number);
		}
	}
	
	/**
	 * 排列组合,用来处理4个数都不同的情况
	 * 如1,2,3,4  可以转化为1,3,2,4   2,3,1,4    1,4,2,3等
	 * 并计算每种的结果
	 */
	public static void getNumber(double[] number){
		for (int i = 0; i < 4; i++) {
			for (int j = 0; j < 4; j++) {
				if(i == j){
					continue;
				}
				for (int k = 0; k < 4; k++) {
					if(k == j || k == i){
						continue;
					}
					for (int m = 0; m < 4; m++) {
						if(m == k || m == j || m == i){
							continue;
						}
						calculation(number[i], number[j], number[k], number[m]);
					}
				}
			}
		}
	}
	
	/**
	 * 给定4个数,当这4个数位置不变时,只改变操作符号,计算所有的可能性
	 * 如1+2+3+4  ,1*2*3*4 , 1-2+3*4 等
	 * 如果能得到24点,就将表达式添加到结果集
	 */
	public static boolean calculation(double num1, double num2, double num3, double num4){
		for (int i = 0; i < 4; i++) {
		/*	第一次计算,保存此时的操作符和计算结果
			此时有3中情况,相当于从4个数中选择2个相邻的数来计算
			如(1-2)-3-4, 1-(2-3)-4, 1-2-(3-4)
			则保存此时第一次计算的结果和操作符*/
			char operator1 = operator[i];
			//根据操作符,先计算第1,2两个数,如输入数字是1,2,3,4  则计算1+2(1-2,1*2,1/2等),
			//这里通过循环来改变操作符,下同
			double firstResult = calcute(num1, num2, operator1);
			//根据操作符,先计算第2,3两个数,如输入数字是1,2,3,4  则计算2+3
			double midResult = calcute(num2, num3, operator1);
			//根据操作符,先计算第3,4两个数,如输入数字是1,2,3,4  则计算3+4
			double tailResult = calcute(num3, num4, operator1);
			for (int j = 0; j < 4; j++) {
				/*	第2次计算,保存此时的操作符和计算结果
				此时有5中情况,相当于从4个数中选择2个相邻的数来计算
				如((1-2)-3)-4, (1-(2-3))-4, (1-2)-(3-4),1-((2-3)-4),1-(2-(3-4))
				则保存此时第2次计算的结果和操作符*/
				char operator2 = operator[j];
				//根据操作符和第1次计算的结果,计算第2次的情况,如第一次计算是(1-2)-3-4,
				//就计算((1-2)-3)-4 ,则第一次计算结果为1-2=-1  -->   即计算-1-3,即firstResult-3
				//下面的原理类似
				double firstMidResult = calcute(firstResult, num3, operator2);
				double firstTailResult = calcute(num3, num4, operator2);
				double midFirstResult = calcute(num1, midResult, operator2);
				double midTailResult = calcute(midResult, num4, operator2);
				double tailMidResult = calcute(num2, tailResult, operator2);
				for (int k = 0; k < 4; k++) {
					//最后1次计算,得出结果,如果是24则保存表达式,原理同上
					char operator3 = operator[k];
					if(calcute(firstMidResult, num4, operator3) == 24){
						String expression = "((" + (int)num1 + operator1 + (int)num2 + ")" + operator2 + (int)num3 + ")" + operator3 + (int)num4;
						results.add(expression);
						flag = true;
					}
					if(calcute(firstResult, firstTailResult, operator3) == 24){
						String expression = "(" + (int)num1 + operator1 + (int)num2 + ")" + operator3 + "(" + (int)num3 + operator2 + (int)num4 + ")";
						results.add(expression);
						flag = true;
					}
					if(calcute(midFirstResult, num4, operator3) == 24){
						String expression = "(" + (int)num1 + operator2 + "(" + (int)num2 + operator1 + (int)num3 + "))" + operator3 + (int)num4;
						results.add(expression);
						flag = true;
					}
					if(calcute(num1, midTailResult, operator3) == 24){
						String expression = "" + (int)num1 + operator3 + "((" + (int)num2 + operator1 + (int)num3 + ")" + operator2 + (int)num4 + ")";
						results.add(expression);
						flag = true;
					}
					if(calcute(num1, tailMidResult, operator3) == 24){
						String expression = "" + (int)num1 + operator3 + "(" + (int)num2 + operator2 + "(" + (int)num3 + operator1 + (int)num4 + "))";
						results.add(expression);
						flag = true;
					}
				}
			}
		}
		return flag;
	}
	
	/**
	 * 给定2个数和指定操作符的计算
	 * @date 2017年12月22日 下午2:47:49 
	 */
	 private static double calcute(double number1, double number2, char operator) {
	        if (operator == '+') {
	            return number1 + number2;
	        } else if (operator == '-') {
	            return number1 - number2;
	        } else if (operator == '*') {
	            return number1 * number2;
	        } else if (operator == '/' && number2 != 0) {
	            return number1 / number2;
	        } else {
	            return -1;
	        }
	    }

}
这是GitHub: https://github.com/1404510094/24-java-.git

  • 9
    点赞
  • 66
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
遗传算法是一种模拟自然选择和遗传机制的优化算法,常用于求解复杂的优化问题。在Python中,实现遗传算法可以按照以下步骤进行: 1. 初始化种群:生成一个由候选解组成的初始种群。每个候选解可以表示为一个染色体,染色体由基因组成。 2. 适应度评估:计算每个候选解的适应度,即目标函数的值。适应度评估决定了哪些解更有可能被选择和遗传到下一代。 3. 选择操作:根据适应度选择一些候选解作为父代。常见的选择方法有轮盘赌选择、锦标赛选择等。 4. 交叉操作:选取一对父代染色体,并通过交叉操作生成子代染色体。交叉操作可以基于染色体的基因位置进行单点交叉、多点交叉等。 5. 变异操作:对子代染色体进行变异操作,引入一些随机性,增加搜索空间的多样性,避免陷入局部最优解。 6. 更新种群:用新生成的子代替换原始种群中的部分个体。 7. 终止条件判断:根据预设条件判断是否满足终止算法的条件,如达到最大迭代次数或目标函数值达到一定阈值。 8. 返回结果:根据终止条件,返回最优解或近似最优解作为算法的输出。 这是一个简单的遗传算法实现框架,在实际应用中可以根据问题的特点进行具体的调整和改进。在Python中,可以使用NumPy库进行数值计算和随机数生成,以及其他辅助库如Matplotlib进行结果可视化等。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值