24點遊戲及其算法

24點遊戲及其算法

1.問題描述

24點是棋牌類益智遊戲,要求結果等於二十四,一起來玩玩吧!這個遊戲用撲克牌更容易來開展。拿一副牌,抽去大小王后(初練也可以把J/Q/K也拿去),剩下1~10這40張牌(以下用1代替A)。任意抽取4張牌(稱爲牌組),用加、減、乘、除(可加括號)把牌面上的數算成24。每張牌必須且只能用一次。如抽出的牌是3、8、8、9,那麼算式爲(9-8)×8×3=24

這裏對問題進行擴展,可以輸入的牌數爲4~52張,其均屬於一副牌,那麼需要計算一串輸入卡牌能否計算出24點,並且輸入運算公式

2.算法分析

其實有一個很簡單的方式,深度優先搜索DFS,但是這個是很無腦的。這裏就不作介紹了。沒啥意思

1、首先我們要對這個問題進行數學抽象。

定義1:對於有理數組成的多重集合S ,f(S) 定義如下:

如果 S 是空集或只包含一個元素,則 f(S)=S ;否則 f(S)=∪ f( ( S-{r1, r2}) ∪ {r} ) ,對於每一個 r=r1+r2 , r1-r2 , r1×r2,r1÷r2(r2≠0),且r1, r2取遍 S 中所有元素的組成的二元組。

定義1說明:要計算集合S中的元素通過四則混合運算所能得到的所有值,我們只需要任取 S 中的兩個元素 r1 , r2 ,分別計算 r1 , r2 的加減乘除運算,然後用所得的結果與 S 中剩下的其他數字進行四則混合運算。只要取遍所有的 r1 ,r2 ,最後得到的所有結果的並集就是 S 中的元素通過四則混合運算所能得到的所有值的集合。根據上述定義,在本問題中,集合 S 就是由輸入中給定的卡牌進行四則運算組成的集合

2、定義2:給定兩個多重集合 S1 , S2,定義

comb( S1, S2 ) = ∪ { r1+r2 , r1-r2, r1×r2, r1÷r2(r2≠0) } 公式(1.1)

其中 ( r1 , r2 ) ∈ S1 × S2。

定義2實際上定義了兩個集合中的元素兩兩進行加減乘除運算所能得到的結果集合。

3、那麼其實就有一個定理:

對於有理數組成的多重集合 S ,如果 S 至少有兩個元素,則

    f(S)=∪ comb( f(S1), f(S - S1) )                      定理(1.2)

其中 S1 取遍 S 的所有非空真子集。

利用上面的定義和定理,例如對於S={1,2,3,4},

f(S) = comb( f({ 1 }), f({ 2,3,4}) )∪ comb( f({ 2 }), f({ 1,3,4}) )… ∪ comb( f({ 1,2 }), f({3,4 }) ) ∪ …;

在這裏的計算中,你會發現計算f({ 2,3,4})時需要計算一次f({ 3,4}),計算f({ 1,3,4})也需要計算一次f({ 3,4}),這樣就產生了計算冗餘。爲了消除冗餘,簡單的方式就是記錄已經計算過的,如果該集合之前已經計算過,直接使用即可。

爲了表述方便,這裏採用位域表示,這樣就可以快速表示集合中有哪些數。譬如還是以上面的例子來說,n(S)=4,那麼就需要用到四位,f[0001]=f[1]代表f({1}),f[0101]=f[5]=f({1,3})這樣。那麼f[2^ n - 1 ] (這裏n=4)=f[15]=f[1111]=f({1,2,3,4})

那麼防止重複計算,到這裏也許就稍微清晰一點了,即存儲中間結果,如果這個已經算過了,那麼直接使用之前已經計算的結果來計算即可,不需再迭代計算。

4、循環方式:

定義計算集合計算符號X,即前面的comb(S1,S2)= f[S1]Xf[S2]

a. 先初始化f[xx1xxx]這種類型的集合,因爲可以直接得到其結果
b. 例如f[0011],即f[0001]Xf[0010]計算而來,並且將這個中間結果存儲,同理可以算出f[0101]=f[0100]Xf[0001],和f[0110]
c. 那麼f[0111]就可以通過f[0011]/f[0101]/f[0110]等集合算出
d. 以此類推算出f[1111]集合的數值
e. 判斷全集是否有24的結果……

a-e中,已經大部分闡述了這個計算方式的思維,但是有一個細節沒有說清楚,那就是,如何計算一個f[ m ] (m<2^n-1)

5、計算f[m] (m<2^n-1)

因爲採用位域表示,那麼設i從0開始,到m,

如果m&i=i(這裏就說明i位域的元素是均屬於m集合,那麼就可以進行集合運算comb(或者說是X))

即計算f[i]Xf[m-i]

一直計算到i=m爲止,到這裏你會發現當m-i’ = i時,會有一次重複計算,所以這裏只需要計算i < m-i的場景即可。

6、僞代碼如下

1.  for i ← 0 to 2^n-1
2.    do F[i]←Φ; 
3.  for i ← 0 to n-1
4.    do F[2^i]← {x[i]}; 
5.  for x ← 1 to 2^n-1 do
6.  begin 
7.    for i ← 1to x-1 do
8.    begin 
9.      if x∧i=i then 
10.     begin 
11.       j ← x – i; 
12.       if i < j 
13.         then F[x] ← F[x] + comp(F[i],F[j]); 
14.     end; 
15.   end; 
16. end;                  
17. return F[ 2 n ?1] ; 

3.進行的優化

  1. 丟棄除不盡
  2. 丟棄過大,不超過24 * 13 * 2
  3. -和/考慮順序,+和*不需考慮順序
  4. 乘法考慮越界,越界就丟棄。

4.示例代碼

代碼見github,順便也實現了輸入計算公式,那個是採用指針的想法,指向計算符號兩端的值(大概是這個意思)

https://github.com/Miss-you/24point

5.計算空間優化

這個因爲需要存儲所有的計算結果,但內存是有限的

另外還需要計算基本是優化過後的全集,當n > 12之後就會發現很花時間了。這個我也找到了優化的方式。下次再寫吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章