傳教士與野人過河問題 —— 人工智能實驗算法


問題描述

  有 N 個傳教士和 N 個野人來到河邊渡河,河岸有一條船,每次至多可供 k 人乘渡。問:傳教士為了安全起見,應如何規划擺渡方案,使得任何時刻, 河兩岸以及船上的野人數目總是不超過傳教士的數目(否則不安全,傳教士有可能被野人吃掉)。 即求解傳教士和野人從左岸全部擺渡到右岸的過程中,任何時刻滿足 M (傳教土數) ≥ C 野人數)和 M+C≤k 的擺渡方案。

 寫在前面

傳教士與野人過河問題是人工智能里面非常經典的算法題,曾經是2012年360公司的面試題,因此網上有各種各樣的解決思路和代碼設計,但是我發現網上的算法思路非常好,代碼設計的盡管多種多樣,但都不滿足我們今天這個項目需求,大部分都是規定好了3個傳教士和3個野人,船上最多只能做2人。這樣的限制是不符合今天這種需求的,因為今天的問題描述中並沒有說明會有幾個傳教士和野人,只能保證傳教士和野人一般多,也沒有說明一條船最多做幾個人,因此網上大部分代碼案例不合題。再有一種就是允許我們輸入有多少人和船最多坐多少人,但是他沒有找出所有的方案。因此,今天就自己動手做一下這個算法。

算法思路

  這個算法很經典,網上有很多的解題思路,我也看了很多,有一個感覺講的非常不錯的推薦一下,就是 CSDN-魏宇軒 前輩的博文,講解的非常棒,思路很清晰,而且他還使用 C語言寫了出來,但是我運行出了問題,一直沒有搞通,於是自己用 C# 寫了一個,其中算法設計思路是按照 魏宇軒 前輩的思路走的。

算法分析

 參考各3人渡河,船最多載2人的算法思路。最總項目人數自定義。

  初始狀態:左岸,3野人,3傳教士;右岸, 0野人,0傳教士;船停在左岸,船上有0個人。
  目標狀態:左岸,0野人,0傳教士;右岸, 3野人,3傳教士;船停在右岸,船上有0個人。
  將整個問題抽象成怎樣從初始狀態經一系列的中間狀態從而達到目標狀態,狀態的改變是通過划船渡河來引發的。
  根據要求,共得出以下5中可能的渡河方案:
    (1)渡2傳教士
    (2)渡2野人
    (3)渡1野人1傳教士
    (4)渡1傳教士
    (5)渡1野人
  本程序使用類來定義狀態結點,使用集合存儲狀態結點,使用遞歸的思想來尋找目標狀態。

程序詳細執行流程如下

  首先,包含狀態(首次為初始狀態)的結構體結點(已存入結構體數組)傳入處理函數,然后判斷該傳入結點狀態是否為目標狀態,是則遍歷打印結構體數組,打印完成之后,返回遞歸調用處,順序執行之后代碼(此步驟關系到是否能找到所有過河路徑);否則繼續判斷是否該傳入結點已存在於結構體數組當中,如存在,不再往下執行,返回遞歸調用處,順序執行之后代碼;若不存在,則繼續判斷該傳入狀態的人數是否合理(是否出現人物數量小於0的情況等),若不合理,返回遞歸調用處,順序執行之后代碼;若合理,則繼續判斷傳教士和野人人數限制條件,即在傳教士人數不為0的情況下,野人人數是否大於傳教士人數,若大於則出現吃人的情況,也就是說該傳入狀態也不合理,則返回遞歸調用處,順序執行之后代碼;若不滿足大於條件,則說明該狀態是路徑轉態,也就是合理的,那么進行五種渡河方案的依次變換,首先為第一種渡河方案,兩個傳教士過河(注意:此處的5中渡河方案沒有固定順序,也可以是其他渡河方案),那么對該傳入狀態的左岸和右岸的傳教士人數和野人人數進行增減(若為左岸到右岸,則左岸人數減,右岸人數加,此處有一個小技巧見本段末尾)。增減完成並改變船的狀態(使用正負一表示,正一為左岸,負一為右岸)以后就產生了一個新的狀態,將該狀態存入結構體數組,之后此處又遞歸調用處理函數,將新產生的轉態結點傳入,再次進行上述條件限制判斷。若在該判斷途中被返回至遞歸調用處,說明該狀態不合理,則此時將已經存入結構體數組的狀態結點移出結構體數組,然后程序順序執行,進行下一個渡河方案的處理,也就是說,此時的處理是對上一個傳入結點的操作(因為剛傳入的已經移出了);若在判斷途中未被返回至遞歸調用處,也就是說,傳入的結點合理了,那么又開始從第一種渡河方案開始對該傳入狀態進行操作。按照上述過程循環執行,直到出現目標狀態,回到本段開頭,遍歷結構體數組,打印渡河路徑結點信息。完成以后,返回遞歸調用處,順序執行之后代碼,此后的操作是在尋找其他渡河路徑。原理為:由於該處理函數末尾存在return語句(關鍵),所以在找到目標狀態並返回之后,目標轉態結點同樣會被移出結構體數組,然后在其上一個結點開始順序往下執行操作之后的一種渡河方案,查看是否在該結點處,還有其他渡河方案可以達到目標狀態,若有則同樣按上述方法執行(打印輸出),若執行完后面的所有渡河方案,發現都沒有能夠達到目標狀態的結點,則會執行末尾的返回語句,返回之后,該狀態結點也會被移除(關鍵),那么此時操作的狀態結點就是上上個結點狀態,對其進行其后的渡河方案操作。按照此法,不斷往后退,直到所有結點都被移除,此時說明已經完成所有渡河路徑的搜索(深度)。至此,本程序的執行過程敘述完畢。

  小技巧:從左岸到右岸,和從右岸到左岸的狀態變化是不一樣的,前者左岸的人數減,右岸的人數加;后者左岸的人數加,右岸的人數減。我們不應單獨再寫程序來處理,而是應該使用船的轉態帶入計算來處理,注意,此技巧在於船的轉態使用正負一來表示,而不應該是1和0,以及其他表示方法。為什么這么說?因為任何數乘以一,其本身都不會改變(有我也不會承認)。而正負號在此起到關鍵作用,我們使用正負一去乘以五種渡河方案的改變數值,從而得到的就是我們變換的正確結果,不論左岸右岸,都是正確合理的。

項目關鍵代碼

主要用來判斷這條方案是否可行。

        // 是否重復操作
            for (int i = 0; i < index; i++)
            {
                if (m.left_c == m_fun[i].left_c && m.left_y == m_fun[i].left_y)
                {
                    if (m.boat_location == m_fun[i].boat_location)
                    {
                        return 0;
                    }
                }
            }

            // 人數是否合理
            if (m.left_c < 0 || m.left_y < 0 || m.right_c < 0 || m.right_y < 0)
            {
                return 0;
            }

            // 傳教士的人數是否大於等於野人
            if ((m.left_c < m.left_y && m.left_c != 0) || (m.right_c < m.right_y && m.right_c != 0))
            {
                return 0;
            }

遞歸算法,主要是用來計算船的渡河載客可能性。

           // 遞歸算法
            for (int cchuan = chuan; cchuan >= 0; cchuan-- )
            {
                for (int ychuan = chuan; ychuan >= 0 ; ychuan--)
                {
                    if ((cchuan >= ychuan && cchuan + ychuan <= chuan && cchuan + ychuan>0) || (cchuan < ychuan && cchuan == 0))
                    {
                        mm.left_c = m.left_c - cchuan * m.boat_location;
                        mm.left_y = m.left_y - ychuan * m.boat_location;
                        mm.right_c = m.right_c + cchuan * m.boat_location;
                        mm.right_y = m.right_y + ychuan * m.boat_location;
                        mm.boat_location = (-m.boat_location);
                        index = index + 1;
                        m_fun.Insert(index, mm);
                        mCalculation(m_fun[index]);
                        index = index - 1;
                    }
                }
            }

 

最終效果

界面打開,默認傳教士和野人的渡河人數都是3人,船的最大載客量為2人,可以自己修改。

  

設置完人數后,點擊“開始計算渡河方案”按鈕,開始計算所有可行方案。

   

【問題】

  根據設置人數和最大載人量的不同,算法可執行方案大不同,因為代碼中並沒有使用多線程,在計算過程之中會存在運算數據過大,計算時間過長,在計算的過程當中出現程序界面卡死,直到利用足夠的時間計算完成為止。

  資料參考自:CSDN-魏宇軒:https://blog.csdn.net/qq_36260974/article/details/84404168

項目代碼

    https://github.com/wjw1014/CrossingtheRiver (僅供參考)


免責聲明!

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



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