アルゴリズムシリーズ14:オオカミ、ヒツジ、野菜、農家が川を渡る

タイトルの説明:農家はオオカミ、ヒツジ、野菜、および自分自身を川の反対側に輸送する必要があります。ボートを漕ぐことができるのは農家だけで、ボートは比較的小さいです。農家に加えて、一度に1つしか輸送できません。厄介な問題があります。農民が監視しなければ、羊は野菜を盗み、狼は羊を食べるでしょう。農家が自分で安全に川を渡れるようにこれらを準備する方法を検討してください。

 

        このトピックでは、人々の高速な論理演算と短期記憶を調べます。オオカミ-「羊-」カイの食物連鎖において、「羊」は重要な位置にあり、問題を解決するための指針となるイデオロギーは、「羊」を「オオカミ」と「カイ」から常に分離することです。 「羊」は常に川を渡って取られる最後でなければなりません。答えを見てください:

 

農夫は川を渡って羊を連れて行きます

農家が帰ってきた

農夫はオオカミを川を渡って連れて行きました

農夫は羊と共に帰る

農家は野菜を川に運びます

農家が帰ってきた

農夫は川を渡って羊を連れて行きます

<終了>

 

別の答えを見てください:

農夫は川を渡って羊を連れて行きます

農家が帰ってきた

農家は野菜を川に運びます

農夫は羊と共に帰る

農夫はオオカミを川を渡って連れて行きました

農家が帰ってきた

農夫は川を渡って羊を連れて行きます

<終了>

 

問題の解決は羊を中心に展開します。

         上記で2つの回答がありましたが、この質問にはいくつの回答がありますか?答えはコンピュータで網羅的である必要があります。コンピューターを使用してこの問題を解決するための鍵は、状態トラバーサルです。これは、「アルゴリズムシリーズ-3バケットの水共有の問題」の記事で述べた状態トラバーサルと同じです。最終的な分析では、有限状態マシンです。農家、オオカミ、ヒツジ、野菜は、それらの位置関係に応じて多くの状態を持つことができますが、状態の総数はまだ限られています。このアルゴリズムは、初期状態から最終状態への遷移が見つかるまで、これらの有限状態の間を行き来しますトピックの要件によれば、この「道」のすべての州は合法的な州でなければなりません。

         状態モデリングアルゴリズムでも状態遷移アルゴリズムでも、この質問は「8リットルの水を3つのバケツで均等に分割する」より簡単です1つはステータスです。農家、オオカミ、ヒツジ、野菜は4つの独立したアイテムです。ステータスは非常に単純で、川を渡るか、渡らないかです。各アイテムのステータスは常に1つだけです。HERE」を使用して川を渡ったことがないことを示す場合、「THERE」を使用して川を渡ったことを示し、[農民、狼、羊、皿]の四角形を使用して特定の瞬間の状態を示します。この質問の状態空間は[ここここHEREHERE]は状態ツリーのルートであり、状態ツリーのリーフノードが状態[THERETHERETHERETHERE]の場合、ルートからリーフノードへの状態シーケンスが問題であることを意味します。 1つのソリューション。

この質問の状態遷移アルゴリズムは、依然として状態空間内のすべての状態の深さ優先探索です。オオカミ、ヒツジ、野菜はボートを漕ぐことができないため、状態遷移アルゴリズムも非常に簡単です。3バケット8リットルの水を分割する」問題は必要ありません。その場合、変換方法(注ぐアクション)は順列と組み合わせによって決定する必要があります。この質問には、次の8つの固定状態変換操作(河川横断アクション)しかありません。

農民は一人で川を渡ります。

農夫はオオカミを川を渡って連れて行きました。

農民は羊を川を渡って連れて行きます。

農夫は川を渡って野菜を持ってきました。

農民は一人で帰ります。

农夫带狼返回;

农夫带羊返回;

农夫带菜返回;

 

        本题的广度搜索边界就是这8个动作,依次对这8个动作进行遍历最多可以转换为8个新状态,每个新状态又最多可以转化为8个新新状态,就形成了每个状态节点有8个(最多8个)子节点的状态树(八叉树)。本题算法的核心就是对这个状态树进行深度优先遍历,当某个状态满足结束状态时就输出一组结果。

 

        需要注意的是,并不是每个动作都可以得到一个新状态,比如“农夫带狼过河”这个动作,对于那些狼已经在河对岸的状态就是无效的,无法得到新状态,因此这个八叉树并不是满树。除此之外,题目要求的合法性判断也可以砍掉很多无效的状态。最后一点需要注意的是,即使是有效的状态,也会有重复,在一次深度遍历的过程中如果出现重复的状态可能会导致无穷死循环,因此要对重复出现的状态进行“剪枝”。

 

        程序实现首先要描述状态模型,本算法的状态定义为:

33 struct ItemState

34 {

35   ......

43   State  farmer,wolf,sheep,vegetable;

44   Action curAction;

35   ......

45 };

算法在穷举的过程中需要保存当前搜索路径上的所有合法状态,考虑到是深度优先算法,用Stack是最佳选择,但是Stack没有提供线性遍历的接口,在输出结果和判断是否有重复状态时都需要线性遍历保存的状态路径,所以本算法不用Stack,而是用Deque(双端队列)。

 

        整个算法的核心就是ProcessState()函数,ProcessState()函数通过对自身的递归调用实现对状态树的遍历,代码如下:

  291 void ProcessState(deque<ItemState>& states)

  292 {

  293     ItemState current = states.back(); /*每次都从当前状态开始*/

  294     if(current.IsFinalState())

  295     {

  296         PrintResult(states);

  297         return;

  298     }

  299 

  300     ItemState next;

  301     for(int i = 0; i < action_count; ++i)

  302     {

  303         if(actMap[i].processFunc(current, next))

  304         {

  305             if(IsCurrentStateValid(next) && !IsProcessedState(states, next))

  306             {

  307               states.push_back(next);

  308               ProcessState(states);

  309               states.pop_back();

  310             }

  311         }

  312     }

  313 }

参数states是当前搜索的状态路径上的所有状态列表,所以ProcessState()函数首先判断这个状态列表的最后一个状态是不是最终状态,如果是则说明这个搜索路径可以得到一个解,于是调用PrintResult()函数打印结果,随后的return表示终止设个搜索路径上的搜索。如果还没有达到最终状态,则依次从8个固定的过河动作得到新的状态,并从新的状态继续搜索。为了避免长长的switch…case语句,程序算法使用了表驱动的方法,将8个固定过河动作的处理函数放在一张映射表中,用简单的查表代替switch…case语句。映射表内容如下:

  279 ActionProcess actMap[action_count] =

  280 {

  281     { FARMER_GO,                  ProcessFarmerGo                },

  282     { FARMER_GO_TAKE_WOLF,        ProcessFarmerGoTakeWolf        },

  283     { FARMER_GO_TAKE_SHEEP,       ProcessFarmerGoTakeSheep       },

  284     { FARMER_GO_TAKE_VEGETABLE,   ProcessFarmerGoTakeVegetable   },

  285     { FARMER_BACK,                ProcessFarmerBack              },

  286     { FARMER_BACK_TAKE_WOLF,      ProcessFarmerBackTakeWolf      },

  287     { FARMER_BACK_TAKE_SHEEP,     ProcessFarmerBackTakeSheep     },

  288     { FARMER_BACK_TAKE_VEGETABLE, ProcessFarmerBackTakeVegetable }

  289 };

 表中的处理函数非常简单,就是根据当前状态以及过河动作,得到一个新状态,如果过河动作与当前状态矛盾,则返回失败,以FARMER_GO_TAKE_WOLF动作对应的处理函数ProcessFarmerGoTakeWolf()为例,看看ProcessFarmerGoTakeWolf()函数的代码:

  182 bool ProcessFarmerGoTakeWolf(const ItemState& current, ItemState& next)

  183 {

  184     if((current.farmer != HERE) || (current.wolf != HERE))

  185         return false;

  186 

  187     next = current;

  188 

  189     next.farmer    = THERE;

  190     next.wolf      = THERE;

  191     next.curAction = FARMER_GO_TAKE_WOLF;

  192 

  193     return true;

  194 }

        当过河动作对应的处理函数返回成功,表示可以得到一个不矛盾的新状态时,就要对新状态进行合法性检查,首先是检查是否满足题目要求,比如狼和羊不能独处以及羊和菜不能独处,等等,这个检查在IsCurrentStateValid()函数中完成。接着是检查新状态是否和状态路径上已经处理过的状态有重复,这个检查由IsProcessedState()函数完成,IsProcessedState()函数的实现也很简单,就是遍历states,与新状态比较是否有相同状态,代码如下:

  131 bool IsProcessedState(deque<ItemState>& states, ItemState& newState)

  132 {

  133     deque<ItemState>::iterator it = find_if( states.begin(), states.end(),

  134                                              bind2nd(ptr_fun(IsSameItemState), newState) );

  135 

  136     return (it != states.end());

  137 }

        运行程序,最终得到的结果是:

 

Find Result 1:

Unknown action, item states is : 0 0 0 0

Farmer take sheep go over river, item states is : 1 0 1 0

Farmer go back, item states is : 0 0 1 0

Farmer take wolf go over river, item states is : 1 1 1 0

Farmer take sheep go back, item states is : 0 1 0 0

Farmer take vegetable go over river, item states is : 1 1 0 1

Farmer go back, item states is : 0 1 0 1

Farmer take sheep go over river, item states is : 1 1 1 1

 

 

Find Result 2:

Unknown action, item states is : 0 0 0 0

Farmer take sheep go over river, item states is : 1 0 1 0

Farmer go back, item states is : 0 0 1 0

Farmer take vegetable go over river, item states is : 1 0 1 1

Farmer take sheep go back, item states is : 0 0 0 1

Farmer take wolf go over river, item states is : 1 1 0 1

Farmer go back, item states is : 0 1 0 1

Farmer take sheep go over river, item states is : 1 1 1 1

 

看来确实是只有两种结果。

 

おすすめ

転載: blog.csdn.net/orbit/article/details/7563220