top>

宣教師と人喰い土人 Missionaries and Cannibals

n人の人喰い土人とn人宣教師が川を渡ろうとしています。ボートはb人乗りで人喰い土人と宣教師のどんな組み合わせでもこぐことができます。
ただし、宣教師の人数が川の両岸、またボートの中でも人喰い土人の人数より少ないと食べられてしまいます。人喰い土人と宣教師が全員無事に川を渡る方法をすべて見つけなさい、という問題です。

川渡り問題というくくりに入るもので、類似問題は多数あります。
ここで取り上げる「宣教師と人喰い土人」の問題は、私が大学生時代の授業の課題で出されたものの一つです。
当時(1984年)は自分のパソコンを持っていませんでしたし、プログラムもBASICしか知りませんでしたので非常に苦労しました。大学の研究室に1台だけあったNECのPC8801(8001かも)を占有して作ったプロブラムを30分のカセットテープに録音?(ピーーとかいう音を確かに録音)した覚えがあります。そういえば、当時私は、ヘキソミノという箱詰めパズルにはまっていて、これをなんとかプログラムで解けないものかと素人ながら考えていたものでした。
数学科の友人から誰かがやってるみたいだ、とかいう情報もありましたが、当時はインターネットとかメールとかもなく、そのままになっていました。

出力結果

横道にそれました。まず、下の出力結果を見て下さい。3人の宣教師(M)と3人の人喰い土人(C)の場合について川を渡る方法を列挙したものです。ボートは2人乗りです。全部で解は4つ。
解1について説明します。
状態0は   MMM CCC /                 --------/の左側すなわち左岸に宣教師Mが3人、人喰い土人Cが3人いる初期状態です。
状態1は     MMM C      /             CC-------左岸のCが2人減って、右岸にCが2人移りました。
状態2は     MMM CC    /             C  -------Cが左岸に1人戻ってきました。
・・・・
ということで
最終状態11                /  MMM CCC-------------全員右岸に移動しました。

出力画面1 出力画面1

※このページは「C言語辞典→アルゴリズム(http://chaste.web.fc2.com/Reference.files/Algo.html)」の「宣教師と人食い人」を参考にしています。
プログラムを作るには(アルゴリズムを考えるには)まず「解の出力結果をイメージする」ことが重要と思います。
出力結果をみて、解の内容が直感的にわることが大事です。

解の表示関数 found(int n)

宣教師(Missionaries)の人数は M、人喰い土人(Cannibals)の人数は C、ボート(boat)の定員は Bとします。
解の表示は、printf(…)で実行しています。

#include <stdio.h>
#include <stdlib.h>
 
#define M  3  /*宣教師の人数*/
#define C  3  /*人喰い土人の人数*/
#define B  2  /*ボートの定員*/
 
int np, solution; /*solutionは解No */
unsigned char mb[(B+1)*(B+2)/2];  /*mbはボートの宣教師数*/
unsigned char cb[(B+1)*(B+2)/2];  /*cbはボートの人喰い土人数*/
unsigned char mh[2*(M+1)*(C+1)]; /*mhは左岸の宣教師数*/
unsigned char ch[2*(M+1)*(C+1)]; /*chは左岸の人喰い土人数*/
unsigned char flag[M+1][C+1];
出力画面1
void found(int n)    /*解の表示関数の定義*/
{
int i;
static char mmm[] = "MMMMMMMMMM";
static char ccc[] = "CCCCCCCCCC";

printf("解 %d\n", ++solution);
for (i=0; i<=n; i++) {
  printf("%4d %-*.*s %-*.*s  /  %-*.*s %-*.*s\n",
               i,  M, mh[i], mmm, C, ch[i], ccc,
                   M, M-mh[i], mmm, C, C-ch[i], ccc);
  }  
}    

ここで、
printf("%4d %-*.*s %-*.*s  /  %-*.*s %-*.*s\n",  i,  M, mh[i], mmm, C, ch[i], ccc, M, M-mh[i], mmm, C, C-ch[i], ccc);
について説明します。
「解 1」の次の行からの表示に関する命令です。

%4dというのは、4桁の十進数。
%-*.*sというのは、
 -:左詰め
 *:桁数
   未定なのでワイルドカード*を使っています。
 .*:表示する文字数
   同じく未定なのでワイルドカード*を使っています。
 s:変換指定子(文字を表す)

更新:2011年9月17日
最終更新:2012年7月24日

再帰的に試す関数 try(void)

void try(void)
{
static i=0;
int j, m, c;

i++;
for (j=1; j<np; j++) {
  if (i & 1) {                          /*iが奇数:奇数回目は向こうに行く*/ ビット演算子&を使ってiが奇数か偶数か判定しています。
  
    m=mh[i-1]-mb[j]; c=ch[i-1]-cb[j];
    } else {                              /*iが偶数:偶数回目はこちらに来る*/
    m=mh[i-1]+mb[j]; c=ch[i-1]+cb[j];
    }
    if (m<0 || c<0 || m>M || c>C || (flag[m][c] & (1<<(i & 1)))) continue; 
mh[i]=m; ch[i]=c; 
if (m==0 && c==0) found(i);
else {
 
    flag[m][c] |=1 << (i & 1);  try();
flag[m][c] ^=1 << (i & 1);
 
    }    
  }
i--;
     
}        

top>
最終更新:2011年9月26日