先看第一个简单的博弈入门问题,这是一个取球问题,题目如下:
今盒子里有n个小球,A、B两人轮流从盒中取球,每个人都可以看到另一个人取了多少个,也可以看到盒中还剩下多少个,并且两人都很聪明,不会做出错误的判断。
我们约定:
每个人从盒子中取出的球的数目必须是:1,3,7或者8个。
轮到某一方取球时不能弃权!
A先取球,然后双方交替取球,直到取完。
被迫拿到最后一个球的一方为负方(输方)
代码的思想就是:对所有的局面进行枚举,直到找到 必胜点 返回 胜 或者找不到 必胜点 返回 负
也是使用了递归,但是这个程序并没有回溯,是因为该例中的局面相对简单,只是一个整型变量n,在相对复杂参数的情况下,最好还是使用回溯的方法使用同一参数。
/* 博弈问题
f(局面 x) ---> 胜负
边界条件处理
for(对我所有可能的走法){
试着走一步 -----> 局面y
胜负 t = f(y);
if(t==负) return 胜
恢复局面
}
return 负
*/
//局面:盒子中球的数目
public class Main{
//局面 : n
public static boolean f(int n){
if(n>=1 && f(n-1)==false) return true;
if(n>=3 && f(n-3)==false) return true;
if(n>=7 && f(n-7)==false) return true;
if(n>=8 && f(n-8)==false) return true;
return false;
}
public static void main(String[] args){
System.out.println(f(10));
System.out.println(f(1));
System.out.println(f(4));
System.out.println(f(150)); //效率不高。用缓冲,已经计算过的局面保存。
}
}
我先拿 | 他先拿 | 局面(球数)每人每次必取 1, 3, 7, 8 | ||||||||||||
必败点(Lost Point) | 我败 | 我胜 | 1 | 3 | 5 | 7 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
必胜点(Win Point) | 我胜 | 我败 | 2 | 4 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 24 |
优化后的代码:采用了数组存储每次可以拿的球数,将几个if语句简化为了一个for循环可以完成的操作。
还是采用递归的手法,将每个处理后的局面交给下一层判断,返回相反的结果。注意这个递归的方法没有单独的设置出口,而是在获胜或者剩下的球数不满1时return。
另外,这个代码附带了时间判断的方法,使用这个系统提供的方法可以在平时判断程序的运行时间是否超出范围。
下面看代码:
public class Test {
public static boolean f(int[] a,int n){
for(int i=0;i<a.length;i++){
if(n>=a[i] && f(a,n-a[i])==false) return true;
}
return false;
}
public static void main(String[] args) {
int[] a = {1,3,7,8};
long begin = System.currentTimeMillis();
System.out.println(f(a,100));
long end = System.currentTimeMillis();
System.out.println("Time:"+(end-begin)/1000.0f);
}
}
对于更复杂的博弈问题,比如井字棋,他的局面不会再是一个整数n就可以表示的范围,但是还可以用上面的方法,对所有可走的局面枚举,检查是否能胜。
对于井字棋特殊的一点就是,除了胜负外还有平局的现象产生,这时如果遇到平局,不要急着返回,应当标记一下,继续向下搜索是否有胜的可能性。
伪代码如下:
//井字棋:有平局的博弈
/*
f(局面 x) ----》 胜负平
{
tag = 负
for(对所有的可走位置){
试走 ----- 局面y
结果 t = f(y);
if(t == 负) return 胜
if(t == 平) tag = 平//只能说不是最好的结果,不能return
}
return tag;
}
*/
上面是取球问题的解法,下面再看一个更深入的博弈问题,03年的蓝桥杯决赛,高僧斗法。
import java.util.*;
public class Main{
static boolean f(int[] x){
for(int i=0;i<x.length-1; i++){
for(int k=x[i]+1; k<x[i+1]; k++){
int temp = x[i];
x[i] = k;
try{
if(f(x)==false) return true;
}
finally{
x[i] = temp;
}
}
}
return false;
}
public static void test(){
Scanner scanner = new Scanner(System.in);
String[] ss = scanner.nextLine().split(" ");
int[] x = new int[ss.length];
for(int i=0; i<ss.length; i++) x[i] = Integer.parseInt(ss[i]);//0 -- (length-1)
for(int i=0; i<x.length-1; i++){ //i 0 -- length-2
for(int k=x[i]+1; k<x[i+1]; k++){ //x[i]+1 -- x[i+1]-1
//对第i个小和尚的走法进行枚举
int temp = x[i];//保存原来的位置,以便回溯
x[i] = k;//赋予新值
try{
if(f(x)==false){
System.out.println(temp + " " + k);
}
}
finally{
x[i] = temp;//回溯
}
}
}
}
public static void main (String[] args) {
test();
}
}