棋牌AI常用算法和技巧


  寫棋牌AI經常需要搜索所有非空真子集,舉個例子

假設手牌{1,2,3,4},那么我們可能需要搜索以下集合

                                                                   {1,2,3,4}

                                   {1}                  {2}                    {3}                    {4}                -----C

                   {1,2}   {1,3} {1,4}      {2,3} {2,4}             {3,4}                                       -----C

  {1,2,3}{1,2,4}   {1,3,4}            {2,3,4}                                                                      -----C

它有多少個子集呢?

這里根據高中數學,我們會發現每一行都是  n為集合元素個數,m為當前子集元素個數,即為從集合中挑出幾個元素。根據二項式定理:C+C+C+…+C=2n,我們可以得出所有子集個數為2n,又因為要減去空集和它自身,所以最終結果為2n-2。由於要遍歷所有子集,所以我們的算法單從目前實現上來說最佳時間復雜度最優解為O(2n),因為遍歷完這2n-2個子集中每一個,它的時間復雜度不可能低於這個,這里我們可以把這個子集抽象成一棵樹去理解。

    怎么去實現它?最常想到的是DPS:

這里可以用遞歸實現該算法,遞歸通常需要一個結束條件來控制它的遍歷深度,這里很顯然,我們可以用層數來充當停止條件。第二個問題是,如何控制它的廣度,我們清楚二叉樹只有左右子樹,但是非二叉樹的子樹難以確定,這里我們先將手牌排序,然后通過for循環和數組的size來控制它的廣度。

這里說第一個技巧,刪除數組元素通常不夠方便,浪費時間,這里我們用swap將當前要刪除的元素和最后一個元素交換,最后pop_back()即可,這里的時間復雜度為O(1)。

第二個技巧是map問題,減少map的使用,我們寫這個算法時會用到hash原理,習慣於使用STL中的map容器,map容器的數據結構是紅黑樹,紅黑樹有左右指針和顏色。各占4個字節,我們可以采用size0f()來驗證這個內存占用,在VS里加起來確實是12,頂多在加上鍵和值的大小,看起來似乎可以接受,實際上並非如此。因為map中的erase以及clear,不能馬上釋放內存。map有自己的機制回收內存,用erase以及clear之后,如果沒有特殊需求,可以認為那部分內存已經釋放了。map不會馬上釋放刪掉內容的內存,而是會對內存進行預留,如果確實很長時間用不到預留的內存,才會釋放。所以如果你想優化你的內存使用,減少使用map。

第三個技巧是使用map時,通過value查找key,這里可以使用find_if,使用方法,構造pred函數對象,這里關鍵是重載()。如下是find_if代碼

template <class InputIterator, class Predicate> 
InputIterator find_if(InputIterator first, InputIterator last,Predicate pred) 

while (first != last && !pred(*first)) ++first; 
return first; 
}

最后,說下優化,無法從算法實現上優化,我們可以優化這個策略,AI策略是加權求和,我們可以通過計算,推演出某些類型的打法必定是高於另一些的,采用貪心的局部最優

優化掉這些求解。這里不貼代碼。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM