編程之美————24點遊戲算法

一,概述

        二十四點是一種益智遊戲,它能在遊戲中鍛鍊人們的心算,它往往要求人們將四個數字進行加減乘除(允許使用括號)求得二十四。然後將四個數字的計算公式表示出來。


二,中綴表達式求解

         最直接的方法就是採用窮舉法,遊戲中可用的運算符只有四種,四個數字每個只能使用一次。

         1)不考慮括號情況

                4個數全排列:4!=24種

                需要三個運算符,且運算符可以重複:4*4*4=64

                總計:1536

         2)考慮括號(是個難點)

                自己想的加括號:四個數有五種加括號方式:  (AB)CD  、 AB(CD)、 A(BC)D 、 A((BC)D) 、 (AB)(CD)、A(B(CD))

                 錯誤點:這裏添加括號的時候,需要把四個數都看成相乘。需要加兩個括號來列舉比較直觀

                                  AB(CD)  =   (AB)(CD)

                 改正後:((AB)C)D  、 (AB)(CD) 、 (A(BC))D 、 A((BC)D) 、A(B(CD))

                         

                四個運算數五種不同加括號方式的由來。這是一個經典的Catalan數問題。

                這個經典Catalan數問題在組合數學教材上都能找到。原題目是:n 個數相乘, 不改變它們的位置, 只用括號表示不同的相乘順序,令g(n)表示這種條件下構成不同乘積的方法數,令C(n)表示第n個Catalan數。則有g(n)=C(n-1)。前幾個Catalan數爲:C(0)=1,C(1)=1,C(2)=2,C(3)=5,C(4)=14,C(5)=42。所以g(4)=C(3)=5。

                根據Catalan數的計算公式,有g(4)=g(1)g(3)+g(2)g(2)+g(3)g(1)。

                Catalan數的計算公式也同時提供了構造答案的方法。對於4個數,中間有3個位置,可以在任何一個位置一分爲二,被分開的兩半各自的加括號方案再拼湊起來就得到一種4個數的加括號方案:

一個數時:(A),一種

兩個數:g(2)=g(1)g(1),所以是(A)(B)=(AB),一種

三個數:g(3)=g(1)g(2)+g(2)g(1)=(A)(BC)+(AB)(C),兩種

四個數:g(4)=g(1)g(3)+g(2)g(2)+g(3)g(1)

                 =(A)[(B)(CD)+(BC)(D)]+(AB)(CD)+[(A)(BC)+(AB)(C)](D)

                 =A(B(CD)) + A((BC)D) + (AB)(CD) + (A(BC))D + ((AB)C)D

         共有五種。於是寫代碼枚舉這五種加括號的方式即可。這種方法只是一種能得到正確答案的方法,擴展性和效率都極差。而且生成的表達式中也有冗餘括號。


[html] view plaincopy
  1. #include <iostream>  
  2. #include <cmath>  
  3. using namespace std;  
  4.   
  5. const double Threshold = 1E-6;  
  6. const int CardsNumber = 4;  
  7. const int ResultValue = 24;  
  8. double number[CardsNumber];  
  9. string result[CardsNumber];  
  10.   
  11. bool PointsGame(int n)  
  12. {  
  13.      if(n == 1)  
  14.      {  
  15.           // 由於浮點數運算會有精度誤差,所以用一個很小的數1E-6來做容差值  
  16.           // 本書2.6節中討論瞭如何將浮點數轉化爲分數的問題  
  17.           if(fabs(number[0] - ResultValue) < Threshold)//結果等於24  
  18.           {  
  19.                cout << result[0] << endl;//輸出表達式  
  20.                return true;   
  21.           }  
  22.           else  
  23.           {  
  24.                return false;  
  25.           }  
  26.      }  
  27.   
  28.      for(int i = 0; i < n; i++)//第一個數(計算時被兩個數結果替換)  
  29.      {  
  30.           for(int j = i + 1; j < n; j++)//第二個數(計算時候被最後一個數替換)  
  31.           {  
  32.                double a, b;//存放計算的數  
  33.                string expa, expb;//存放表達式中兩個數  
  34.   
  35.                a = number[i];  
  36.                b = number[j];  
  37.                number[j] = number[n - 1];//去除第二個數  
  38.   
  39.                expa = result[i];  
  40.                expb = result[j];  
  41.                result[j] = result[n - 1];//表達式去除  
  42.   
  43.                result[i] = '(' + expa + '+' + expb + ')';  
  44.                number[i] = a + b;//去除第一個數  
  45.                if(PointsGame(n - 1))  
  46.                     return true;  
  47.   
  48.                result[i] = '(' + expa + '-' + expb + ')';  
  49.                number[i] = a - b;  
  50.                if(PointsGame(n - 1))  
  51.                     return true;  
  52.   
  53.                result[i] = '(' + expb + '-' + expa + ')';  
  54.                number[i] = b - a;  
  55.                if(PointsGame(n - 1))  
  56.                     return true;  
  57.   
  58.                result[i] = '(' + expa + '*' + expb + ')';  
  59.                number[i] = a * b;  
  60.                if(PointsGame(n - 1))  
  61.                     return true;  
  62.   
  63.                if(b != 0)  
  64.                {  
  65.                     result[i] = '(' + expa + '/' + expb + ')';  
  66.                     number[i] = a / b;  
  67.                     if(PointsGame(n - 1))  
  68.                          return true;  
  69.                }  
  70.                if(a != 0)  
  71.                {  
  72.                     result[i] = '(' + expb + '/' + expa + ')';  
  73.                     number[i] = b / a;  
  74.                     if(PointsGame(n - 1))  
  75.                          return true;  
  76.                }  
  77.   
  78.                number[i] = a;//將本次循環的結果消除,繼續測試下一對數  
  79.                number[j] = b;  
  80.                result[i] = expa;  
  81.                result[j] = expb;  
  82.           }  
  83.      }  
  84.      return false;  
  85. }  
  86.   
  87. int main()  
  88. {  
  89.     int x;  
  90.     for(int i = 0; i < CardsNumber; i++)  
  91.     {  
  92.          char buffer[20];  
  93.          cout << "the " << i << "th number:";  
  94.          cin >> x;  
  95.          number[i] = x;  
  96.          itoa(x, buffer, 10);  
  97.          result[i] = buffer;  
  98.     }  
  99.     if(PointsGame(CardsNumber))  
  100.     {  
  101.          cout << "Success." << endl;  
  102.     }  
  103.     else  
  104.     {  
  105.          cout << "Fail." << endl;  
  106.     }  
  107. }  

三,分支限界法求解


[html] view plaincopy
  1. #include <iostream>  
  2. #include <set>  
  3. #include <string>  
  4. #include <cmath>  
  5. using namespace std;  
  6.   
  7. #define N   4   // 4張牌,可變  
  8. #define RES 24  // 運算結果爲24,可變  
  9. #define EPS 1e-6  
  10.   
  11. struct Elem  
  12. {  
  13.     Elem(double r, string& i):res(r),info(i){}  
  14.     Elem(double r, char* i):res(r),info(i){}  
  15.     double res; // 運算出的數據  
  16.     string info; // 運算的過程  
  17.     bool operator<(const Elem& e) const  
  18.     {  
  19.         return res < e.res; // 在set的紅黑樹插入操作中需要用到比較操作  
  20.     }  
  21. };  
  22.   
  23. int A[N];   // 記錄N個數據  
  24. // 用二進制數來表示集合和子集的概念,0110表示集合包含第2,3個數  
  25. set<Elem> vset[1<<N];   // 包含4個元素的集合共有16個子集0-15  
  26.   
  27. set<Elem>& Fork(int m)  
  28. {  
  29.     // memo遞歸  
  30.     if (vset[m].size())  
  31.     {  
  32.         return vset[m];  
  33.     }  
  34.     for (int i=1; i<=m/2; i++)  
  35.         if ((i&m) == i)  
  36.         {  
  37.             set<Elem>s1 = Fork(i);  
  38.             set<Elem>s2 = Fork(m-i);  
  39.             set<Elem>::iterator cit1;  
  40.             set<Elem>::iterator cit2;  
  41.             // 得到兩個子集合的笛卡爾積,並對結果集合的元素對進行6種運算  
  42.             for (cit1=s1.begin(); cit1!=s1.end(); cit1++)  
  43.                 for (cit2=s2.begin(); cit2!=s2.end(); cit2++)  
  44.                 {  
  45.                     string str;  
  46.                     str = "("+cit1->info+"+"+cit2->info+")";  
  47.                     vset[m].insert(Elem(cit1->res+cit2->res,str));  
  48.                     str = "("+cit1->info+"-"+cit2->info+")";  
  49.                     vset[m].insert(Elem(cit1->res-cit2->res,str));  
  50.                     str = "("+cit2->info+"-"+cit1->info+")";;  
  51.                     vset[m].insert(Elem(cit2->res-cit1->res,str));  
  52.                     str = "("+cit1->info+"*"+cit2->info+")";  
  53.                     vset[m].insert(Elem(cit1->res*cit2->res,str));  
  54.                     if (abs(cit2->res)>EPS)   
  55.                     {  
  56.                         str = "("+cit1->info+"/"+cit2->info+")";  
  57.                         vset[m].insert(Elem(cit1->res/cit2->res,str));  
  58.                     }  
  59.                     if (abs(cit1->res)>EPS)  
  60.                     {  
  61.                         str = "("+cit2->info+"/"+cit1->info+")";  
  62.                         vset[m].insert(Elem(cit2->res/cit1->res,str));  
  63.                     }  
  64.                 }  
  65.         }  
  66.     return vset[m];  
  67. }  
  68.   
  69. int main()  
  70. {  
  71.     int i;  
  72.     for (i=0; i<N; i++)  
  73.         cin >> A[i];  
  74.     // 遞歸的結束條件  
  75.     for (i=0; i<N; i++)  
  76.     {  
  77.         char str[10];  
  78.         sprintf(str,"%d",A[i]);  
  79.         vset[1<<i].insert(Elem(A[i],str));  
  80.     }  
  81.     Fork((1<<N)-1);//開始1111 表示四個數   
  82.     // 顯示算出24點的運算過程  
  83.     set<Elem>::iterator it;  
  84.     for (it=vset[(1<<N)-1].begin();   
  85.        it!=vset[(1<<N)-1].end(); it++)  
  86.         {  
  87.             if (abs(it->res-RES) < EPS)  
  88.                 cout << it->info << endl;  
  89.     }  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章