圍棋有幾種變化是一個老問題了,比較粗淺的說法是3的19乘19次方,意思就是棋盤上每個點有空、黑、白三種狀態,總共有19*19個點,所以得出這個結果。但實際上並沒有那麼多,因為在那麼多狀態中,有很大一部分是不可能出現的狀態,也就是盤面上有死棋的狀態。比如整個棋盤上布滿棋子的狀態都是不可能的,而這種狀態就有2的19*19次方之多。
所以很久以前我曾經在論壇上提出過“圍棋合理的變化到底有幾種”的問題。我原先想從純組合數學的角度來解決,試圖得出一個簡單的運算式結果來。但想了半天,也沒有一種合理的思路。寫個程式來計算當然是可能的,但當初論壇上似乎所有人都說這沒意義。我心有不甘,最近終於藉著學習java的機會寫了個這麼粗淺的程式(GoCount.java)。
這個程式的演算法無疑是很直接和低效的,它就是對一個n*n的棋盤,枚舉所有3^(n*n)種情況,對每種情況判斷每個點是否都是活棋,如果每個點都是活棋,則全域是一個合理的局面。對每個點是否活的判斷標準(對應isAlive函數)是一個遞迴:一個點存活若且唯若它是一個空點或與這個點相鄰的點上有空點或同色活棋。
這個程式的效率分析如下:
enumAllStatus函數枚舉所有狀況,共執行3^(n*n)次。在每一次執行enumAllStatus中,要調用isValid函數(判斷是當前局面是否合理),它又調用:resetVisited函數(重設每個點的訪問標誌),執行n*n次; isAlive函數(判斷每個點是否活),也執行n*n次。IsAlive函數又是一個遞迴函式,它的遞迴深度不太好估計,我感覺它大概會是與n線性階的一個數。所以isAlive總共執行次數會是o(n^3),相對於resetVistied,它起主要作用。所以整個演算法的時間複雜度是o(3^(n*n)*n^3)。
我計算了一些結果,設一個n*n的棋盤,它的所有合理變化的數字用V(n)表示的話,則V(1)=1,V(2)=57, V(3)=12675,V(4)= 24318165. 在我的機器上,計算V(3)用了125ms,計算V(4)用了498907ms,約8分多鐘。而如果用計算V(3)的時間和前面的時間複雜度估算計算V(4)的時間(忽略次要項和常係數),結果將是648000ms,與實際情況在同一數量級上,所以我感覺我的時間複雜度估計還是大致準確的。
但這樣的話,估計計算V(5)需要的時間大約在幾百天的數量級上。另一個問題是,目前我用的是int的變數來計數,java中int型最大值是2^31-1,它只能處理n=4的情況。即使改成long型,它也只能處理n=5,你可以自己算一下。當然,這是一個次要問題,演算法的低效才是主要問題。
另一個有趣的思考是考察V(n)/3^(n*n)的值,也就是合理的變化占所有變化的比值。根據目前結果:
n=2: V(2)=57, 57/3^4= 0.7037
n=3: V(3)=12675, 12675/3^9= 0.6440
n=4: V(4)=24318165, 24318165/3^16=0.5649
似乎是越來越小,那它最終會不會去趨向於一個常量呢?我感覺如果樂觀估計的話V(n)/3^(n*n)會趨向於一個大於0.5的值,至少也是1/3,但苦於找不出證明。我很希望誰能先給出一個它不會趨向於0的證明。
另一方面,你也可以幫我最佳化一下演算法,但主要的最佳化是要使程式不必枚舉3^(n*n)種變化,否則程式不會有質的改善。