取球遊戲_博弈論入門學習
先看第一個簡單的博弈入門問題,這是一個取球問題,題目如下:
今盒子裡有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();
}
}