• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            隨筆 - 51, 文章 - 1, 評論 - 41, 引用 - 0
            數據加載中……

            猜數字的一種解法

                     本文將給出解答猜數字游戲(guess number)的一種算法,最多6步就可猜出結果。這個的解法需要用到部分信息論的知識
                猜數字問題表述如下:答案是09十個數的四位排列(沒有重復元素)。每猜一次,游戲比較答案和輸入這兩組排列,反饋aAbBaA表示有a個數字數值正確位置正確,bB表示b個數字數值正確位置錯誤。
                考慮問題的互動方式,這道題目屬于輸入反饋的問題。每次輸入,從反饋中獲取部分信息,當獲得系統的全部信息時,就能猜出答案。所以解這個問題的關鍵是尋找合適的輸入使反饋的信息量最大。
                這道題的解法思路比較簡單,由于是有窮狀態,而且數量不大,可用窮舉法解答。比較所有可能的輸入,找出最佳輸入(某個標準下)。問題變成了如何衡量輸入的好壞。輸入越好則反饋的信息越大,山農給出的信息量的定義是衡量輸入好壞的標準。信息量是不確定性的大小,如果某個事件的概率為P,則它含有的信息量為log(1/P)。具體的理論可以參考一本關于信息論的書。
                具體到猜數字這個題目,答案有多種可能。得到反饋前,每一個輸入的反饋有不同的可能。得到反饋后,反饋是確定的,不確定性變為0。這樣問題的信息量變小了。前后的信息量之差就是這個輸入能夠得到的信息量。

            統計反饋前這個輸入可能得到的反饋,求出各種反饋的概率,可由下面公式計算出反饋前的信息量:
            H=P1*log(1/P1)+P2*log(1/P2)+...P1P2等分別為不同反饋的概率。
            反饋后,這個輸入的反饋是確定的,信息量為0比較所有輸入能夠得到信息量,就能找出最佳的輸入。

            統計反饋前這個輸入可能得到的反饋

            // 把輸入和所有可能的答案比較,得到所以不同反饋可能出現的次數
            // 輸入參數:input輸入
            // 輸出參數:feedbacks反饋可能出現的次數
            void GetPsbFeedbacksNum(VectorInt
            & feedbacks, const VectorInt& input)
            {
                    
            // 和所有可能的答案比較
                
            for (int j=0; j<ALL_ANS_NUM; j++)
                {
                    
            // 答案是否已經被排除
                    
            if (g_AnsState[j] != 0)
                        continue;
                    feedbacks[GetFeedback(input, g_AllAns[j])]
            ++;
                }
            }

            計算這個輸入能夠獲得的信息量
            // 計算信息量
            // 輸入參數:不同反饋出現的次數
            // 返回值:信息量
            double CalcEntropy(const VectorInt &feedbacks)
            {
                
            int sum = 0;
                
            double entropy = 0.;
                
            for (int j=0; j<feedbacks.size(); j++)
                    sum 
            += feedbacks[j];

                
            for (int j=0; j<feedbacks.size(); j++)
                {
                    
            if (feedbacks[j] == 0)
                        continue;
                    float tmp 
            = (float)feedbacks[j]/sum;
                    entropy 
            += -1*tmp*log(tmp);
                }
                return entropy;
            }

            這樣就得到每個輸入的信息量。下面討論如何枚舉所有可能的輸入。
            輸入的種類有10*9*8*7種,把每一種輸入和所有可能答案比較。這方法比較直觀,但運算次數太多。
            // 得到最佳的輸入,不分類直接比較的方法
            // 最佳輸入存放在g_AnsGuess中
            void GetBestInputNoSort()
            {
                
            double max_entropy = 0.;    // 存放最大的信息量
                VectorInt feedbacks((ANS_SIZE
            +1)*(ANS_SIZE+1), 0);    // 反饋的結果分類

                
            // 比較所有輸入的信息量
                
            for (int i=0; i<ALL_ANS_NUM; i++)
                {
                    
            // 這個輸入的信息量是否是0
                    
            if (g_InputState[i] == 1)
                        continue;

                    
            // 是否只在可能的答案中搜索最佳輸入
            //        if (g_AnsState[i] != 0)
            //            continue;

                    
            // 得到所有可能的反饋
                    feedbacks.assign((ANS_SIZE
            +1)*(ANS_SIZE+1), 0);
                    GetPsbFeedbacksNum(feedbacks, g_AllAns[i]); 

                    
            // 計算信息量
                    
            double entropy = CalcEntropy(feedbacks);

                    
            // 排除信息量為0的輸入
                    
            if (entropy < 0.00001)
                    {
                        g_InputState[i] 
            = 1;
                        continue;
                    }

                    
            // 求最大的信息量
                    
            if (max_entropy < entropy)
                    {
                        max_entropy 
            = entropy;
                        g_AnsGuess 
            = g_AllAns[i];
                    }
                }    
            }

            由于第一次輸入,各種輸入能夠得到的信息量是一樣的,因此第一次可以不比較,任給一組輸出。改進后的代碼如下:
            // 得到最佳的輸入,不分類直接比較的方法,但優化過第一次輸入。
            // 最佳輸入存放在g_AnsGuess中
            void GetBestInputIpvFst()
            {
                
            // 如果是第一次,所有輸入的信息量是一樣的,任選一個
                
            if (g_isFirstTime)
                {
                    g_isFirstTime 
            = 0;
                    g_AnsGuess 
            = g_AllAns[0];
                }
                
            else
                    GetBestInputNoSort();
            }

            第一次輸入時10個元素之間是沒有分別的。沿著這個思路考慮,第二次輸入時,6個沒有參與輸入的元素,也沒有分別。舉例來說,第一次輸入{0,1,2,3},則4,5,6,7,8,9六個元素屬同一類元素,在第二次輸入時可以相互替換。如{0,1,2,4}和{0,1,2,5}兩組輸入能夠得到的信息量應該相同。把元素分類枚舉則可以優化算法。
            分類方法如下,參入輸出的元素單獨成一類,沒有參與的元素是一類。第一次輸入時,只有一類元素,這類元素個數為10。第二次輸入時,有五類元素,有四類元素個數為1,一類元素個數為6。依次類推。關于如何枚舉見《從集合中枚舉子集》
            // 得到最佳的輸入,元素分類的方法
            // 最佳輸入存放在g_AnsGuess中
            void GetBestInputSort()
            {
                
            double max_entropy = 0.;    // 存放最大的信息量
                VectorInt feedbacks((ANS_SIZE
            +1)*(ANS_SIZE+1), 0);// 反饋的結果分類

                
            // 初始化枚舉
                CSetIterAgent
            <int> iter(g_AnsEle);
                iter.Init(ANS_SIZE, CSetIter::MULT_ORDERED_IN);

                
            // 比較所有輸入的信息量
                VectorInt setsub(ANS_SIZE, 
            0);
                
            while (iter.GetNextSubset(setsub, g_AnsEle))
                {
                    
            // 把-1元素具體化,-1表示該元素沒有輸入過
                    AssembleInput(setsub);

            //        // 這個輸入的信息量是否是0
            //        if (g_InputState[GetSetPosition(setsub)] == 1)
            //            continue;

                    
            // 是否只在可能的答案中搜索最佳輸入
            //        if (g_AnsState[GetSetPosition(setsub)] != 0)
            //            continue;

                    
            // 得到所有可能反饋的數目
                    feedbacks.assign((ANS_SIZE
            +1)*(ANS_SIZE+1), 0);
                    GetPsbFeedbacksNum(feedbacks, setsub);

                    
            // 計算信息量
                    
            double entropy = CalcEntropy(feedbacks);

            //        // 排除信息量為0的輸入
            //        if (entropy < 0.00001)
            //        {
            //            g_InputState[GetSetPosition(setsub)] = 1;
            //            continue;
            //        }

                    
            // 求最大的信息量
                    
            if (max_entropy < entropy)
                    {
                        max_entropy 
            = entropy;
                        g_AnsGuess 
            = setsub;
                    }
                }    
                
            // 元素分類
                SortEle();
            }

            分類的方法可以大大提升運算速度,下面的測試會證明這一點。但當參與輸入元素慢慢增加時,分類就不那么重要,而且調用CSetIterAgent也是一筆不小的開銷。將第一種算法和分類法結合:
            // 不分類法和分類法的混合法
            // 輸入參數:num表示當輸入元素個數等于或超過num時用直接法
            void GetBestInputBlend(
            int num)
            {
                
            // 計算沒有輸入過的元素個數
                
            int sum = 0;
                
            for (int i=0; i<g_AnsEle.size(); i++)
                {
                    
            if (g_AnsEle[i] == -1)
                        sum
            ++;
                }
                sum 
            = g_AnsEle.size() - sum;

                
            if (sum >= num)
                    GetBestInputNoSort();
                
            else
                    GetBestInputSort();
            }

            下面給出這些算法的測試結果。測試方法是將10*9*8*7種答案依次用這些算法求解,統計猜出每個答案需要多少次數。一共花費了多少時間。
            1次         1
            2次         12
            3次         261
            4次         2082
            5次         2500
            6次         184
            平均次數:4.51190
            N次是指N次輸入反饋后,就可以確定答案,并不指第N次輸入的反饋是4A0B。
            花費的時間:
            GetBestInputNoSort:       8h也沒有算完
            GetBestInputIpvFst:         9385s
            GetBestInputSort:            1596s
            GetBestInputBlend7:       2151s
            GetBestInputBlend10:     1515s

            這些方法在本質上是一樣的,即在所有的輸入中尋找反饋信息量最大的輸入。下面考慮其他方法,比較這些方法的差別。
            如果縮小尋找范圍,不考慮不是答案的輸入。代碼如下:
            取消GetBestInputNoSort函數中
                    // 是否只在可能的答案中搜索最佳輸入
                    
            if (g_AnsState[i] != 0)
                        continue;
            和GetBestInputSort函數中
                    // 是否只在可能的答案中搜索最佳輸入
                    
            if (g_AnsState[GetSetPosition(setsub)] != 0)
                        continue;
            這兩行注釋。
            測試結果如下:
            1次       1
            2次       16
            3次       228
            4次       1555
            5次       2688
            6次       542
            7次       10
            平均次數:4.70218
            運行時間:
            GetBestInputIpvFst:1375s
            GetBestInputSort:   97s
            縮小范圍后,速度有很多提升,但平均次數增加了。

            再考慮另外一種方法,不從信息量的角度考慮。用第一個可能為答案的排列作為輸入。
            // 得到第一個可能的答案
            void GetFstPsbInput()
            {
                
            for (int i=0; i<ALL_ANS_NUM; i++)
                {
                    
            if (g_AnsState[i] == 0)
                    {
                        g_AnsGuess 
            = g_AllAns[i];
                        break;
                    }
                }
            }
            測試結果:
            1次       1
            2次       15
            3次       211
            4次       1240
            5次       2108
            6次       1203
            7次       252
            8次       10
            平均次數:5.00515
            花費的時間:7s
            這個方法令人吃驚的是它的速度,如此簡單卻又不錯的效果。平均次數要比上面兩個方法差些。
            上面的測試結果是在vc2005的release下編譯測試。

                     本文用信息量大小作為判斷最佳輸入的標準,相比其他算法平均步數較少,但花費的時間較多。如何縮短計算時間將在以后的文章中繼續探討。此外從測試中看出,方法2,3在兩次內猜中答案的次數比方法1多。在這個意義下方法2,3比方法1要優。以后還會在不同的意義下比較各類算法。大家有什么好的想法可以聯系我,大家一起討論。我的郵箱lemene@sina.com.cn

            代碼下載

            posted on 2007-11-26 16:42 lemene 閱讀(5290) 評論(5)  編輯 收藏 引用

            評論

            # re: 猜數字的一種解法  回復  更多評論   

            見過的最好結果6步
            100%
            2008-03-13 12:54 | QQ474073341

            # re: 猜數字的一種解法  回復  更多評論   

            我已經證明了六步不可能猜完所有的數
            149588829
            2008-03-13 14:46 | yczni

            # re: 猜數字的一種解法[未登錄]  回復  更多評論   

            可以六步完成,這里是指最多六步就知道確切的結果。不是指六步就反饋4A0B。有點細微的差別,如果一定要反饋4A0B才算成功,則需要七步。
            2008-03-14 23:52 | lemene

            # re: 猜數字的一種解法  回復  更多評論   

            依靠反饋的熵的最大值來決定最佳猜測感覺沒有依據,我覺得應該是根據每種可能的反饋導致正確答案范圍的減小量來作為信息量比較合適。
            2011-05-06 21:49 | 奔跑

            # re: 猜數字的一種解法[未登錄]  回復  更多評論   

            @奔跑
            本算法基本思想也可以理解“根據每種可能的反饋導致正確答案范圍的減小量來作為信息量”,具體的算法只是這一思想的數學建模。
            2011-05-07 17:52 | lemene
            久久国产成人精品国产成人亚洲| 中文字幕亚洲综合久久菠萝蜜| 亚洲av成人无码久久精品| 伊人热热久久原色播放www| 一级做a爰片久久毛片毛片| 欧美日韩精品久久久久| 久久国产高潮流白浆免费观看| 亚洲综合久久综合激情久久 | 色欲综合久久中文字幕网| 久久精品夜夜夜夜夜久久| 精品人妻伦一二三区久久| 亚洲色欲久久久综合网东京热| 国产精品欧美久久久久无广告| 国产精品乱码久久久久久软件 | 亚洲欧洲日产国码无码久久99| 精品久久无码中文字幕| 亚洲国产成人久久精品99| 99久久99久久| 亚洲狠狠婷婷综合久久蜜芽 | 久久噜噜电影你懂的| 久久人人爽人人爽人人片av麻烦| 久久综合中文字幕| 国产午夜福利精品久久2021| 日本精品久久久久久久久免费| 国产精品一区二区久久| 久久午夜伦鲁片免费无码| 久久精品国产亚洲av麻豆图片| 美女久久久久久| 亚洲国产成人久久一区久久| 国产ww久久久久久久久久| 天天爽天天爽天天片a久久网| 男女久久久国产一区二区三区 | 伊人久久大香线蕉无码麻豆 | 国产精品久久久久无码av| 日本强好片久久久久久AAA| 久久AV高潮AV无码AV| 亚洲av伊人久久综合密臀性色| 久久青青草视频| 欧洲人妻丰满av无码久久不卡| 日韩人妻无码精品久久免费一| 久久亚洲AV成人出白浆无码国产 |