猜数字游戏穷举法(迅雷水晶矿场中的游戏求解)

猜数字是一个比较经典的游戏。有若干的空,每个空可以填入若干种数字,每次填入数字后,提示有几个数字是正确的,有几个数字位置填写对了。如果位置正确的数字和填入数字个数相等则得到正确答案。

这里只针对一种简单的情况(有3个空,每个空可以填入0-3中的一个数字)使用穷举法求解。想了解更多可以看论文https://arxiv.org/pdf/1305.1010.pdf

3个空,每个空填入0-3的数字,则所有的情况有4*4*4=64种可能。随便猜一个数,返回结果可以表示成(0,0)(1,0)(1,1)(2,0)(2,1)(2,2)(3,0)(3,1)(3,2)(3,3)(4,0)(4,1)(4,2)(4,3)(4,4)中的一种,(1,0)表示有1个颜色正确0个位置正确。随便猜一个数字就可以把64中可能分配到不同的结果集中,如果某个结果集中只有一种可能,表示这次猜测能得到该结果,如果结果集中有多种可能则继续猜测。
随便猜测时把64种可能都试一遍,找到平均猜测次数最小的就是我们想要的答案了。代码如下:

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
//存放当前的状态
class Point{
    final int N = 3;//该类只能操作3位数的猜数字游戏   
    int[] colors = new int[N];  
    Point(){}
    Point(int i,int j,int k){
        this.colors[0] = i;
        this.colors[1] = j;
        this.colors[2] = k;
    }
    //获取正确数字个数
    public int rightNumber(Point p){
        int num = 0;
        boolean a[] = new boolean[N];
        for(int i=0;i<N;i++){
            a[i] = false;
        }
        for(int i=0;i<N;i++){
            for(int j=0;j<N;j++){
                if(!a[j]){
                    if(this.colors[i]== p.colors[j]){
                        num++;
                        a[j]=  true;
                        break;
                    }
                }
            }
        }
        return num;
    }
    //位置和颜色都正确的个数
    public int rightStatus(Point p){
        int num = 0;
        for(int i=0;i<N;i++){
            if(this.colors[i] == p.colors[i]){
                num++;
            }
        }
        return num;
    }
}
//查找最优方法
public class GetBestWay {   
    /**
     * 根据猜测进行分组
     * @param possble  所有的可能
     * @param rule  如果用户输入的是rule,把所有可能进行分组 
     * @return   所有分组的情况
     */
    public static List<ArrayList<Point> > getPossibleArray(List<Point> possble,Point rule){
        List<ArrayList<Point> > eligible = new ArrayList<ArrayList<Point> >();
        //因为只有3个数字,颜色正确个数有4种可能,
        //颜色和位置都正确右4种可能,组合起来有16种可能
        for(int i=0;i<16;i++){
            ArrayList<Point> temp = new ArrayList<Point>();
            eligible.add(temp);
        }
        for(int i=0;i<possble.size();i++){
            int r1 = possble.get(i).rightNumber(rule);
            int r2 = possble.get(i).rightStatus(rule);
            //把所有可能分配到不同的集合中
            eligible.get(r1*4+r2).add(possble.get(i));
        }
        return eligible;
    }
    /**
     * 已知用户输入和正确数字个数与正确位置个数,求所有可能
     * @param possble  输入所有的可能
     * @param rule   用户输入
     * @param result   result[0]表示正确数字个数,result[1]表示位置正确个数
     * @return  根据正确数字个数和正确位置个数,有哪些可能能满足
     */
    public static List<Point> getPossible(List<Point> possble,Point rule,int[] result){
        List<Point> eligible = new ArrayList<Point>();
        if(possble.size()==1){
            return eligible;
        }
        for(int i=0;i<possble.size();i++){
            int r1 = possble.get(i).rightNumber(rule);
            int r2 = possble.get(i).rightStatus(rule);
            //与result相符,添加到结果集中
            if(r1 == result[0] && r2 == result[1]){
                eligible.add(possble.get(i));
            }
        }
        return eligible;
    }
    //打印所有可能性
    public static void print(List<Point> possble){
        for(int i=0;i<possble.size();i++){
            for(int j=0;j<possble.get(i).colors.length;j++){
                System.out.print(possble.get(i).colors[j]);
                System.out.print(" ");
            }
            System.out.print("; ");
        }
        System.out.println();
    }
    /**
     * 寻找最优解
     * @param possible 所有的可能
     * @param bestPoint 返回最优选择
     * @return  最优选择平均查找次数
     */
    public static double findBestPoint(List<Point> possible,Point bestPoint){
        int n = possible.size();
        if(n==0)
            return 0;
        if(n==1){
            bestPoint.colors = possible.get(0).colors;
            return 1;
        }
        double min = 999999;
        //因为穷举法速度太慢,这里使用了贪心策略。因为该贪心方法最后求得的结果也是206/64,是可以的
        for(int i=0;i<n;i++){
            double result = 1;
            Point rule = possible.get(i);
            List<ArrayList<Point> > possibleList = getPossibleArray(possible,rule);
            //第16种是正确个数和位置都为3,也就是得到正确结果,不用继续查找
            for(int j=0;j<15;j++){
                List<Point> temp = possibleList.get(j);
                if(temp.size()!=0){
                    result+=temp.size()/(double)n*findBestPoint(temp,new Point());
                }
            }
            if(min>result){
                min = result;
                bestPoint.colors = rule.colors;
            }
        }
        return min;
    }
    public static void main(String[] args) {
        Scanner cin = new Scanner(System.in);
        List<Point> possible = new ArrayList<Point>();
        //初始化,有三个数字要猜。每个数字是0-3中的一种
        for(int i=0;i<4;i++){
            for(int j=0;j<4;j++){
                for(int k=0;k<4;k++){
                    Point t = new Point(i,j,k);
                    possible.add(t);
                }
            }
        }       
        Point bestPoint = new Point();
        int[] rightNum = new int[2];
        while(possible.size()>0){
            if(possible.size()==1){
                System.out.println("当前只有一个选择");
                print(possible);
                break;
            }
            System.out.println("当前集合中包含所有可能为:");
            print(possible);
            //根据当前情况查找最优解
            double result = findBestPoint(possible,bestPoint);
            System.out.println("还需要的平均步数为:"+result);
            System.out.println("推荐最优选择为:"+ bestPoint.colors[0]+" "+bestPoint.colors[1]+" "+bestPoint.colors[2]);
            System.out.println("你的选择是:");
            bestPoint.colors[0] = cin.nextInt();
            bestPoint.colors[1] = cin.nextInt();
            bestPoint.colors[2] = cin.nextInt();
            System.out.println("输出结果是:");
            rightNum[0] = cin.nextInt();
            rightNum[1] = cin.nextInt();
            possible = getPossible(possible, bestPoint, rightNum);
        }
        System.out.println("结束");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值