如何解决野人过河问题? 急!求野人过河问题用c语言实现!!

作者&投稿:徐光 (若有异议请与网页底部的电邮联系)
差不多,你参考下:
野人过河问题属于人工智能学科中的一个经典问题,问题描述如下: 有三个牧师(也有的翻译为传教士)和三个野人过河,只有一条能装下两个人的船,在河的任何一方或者船上,如果野人的人数大于牧师的人数,那么牧师就会有危险. 你能不能找出一种安全的渡河方法呢?
一、算法分析
先来看看问题的初始状态和目标状态,假设和分为甲岸和乙岸:
初始状态:甲岸,3野人,3牧师;
乙岸,0野人,0牧师;
船停在甲岸,船上有0个人;
目标状态:甲岸,0野人,0牧师;
乙岸,3野人,3牧师;
船停在乙岸,船上有0个人;
整个问题就抽象成了怎样从初始状态经中间的一系列状态达到目标状态。问题状态的改变是通过划船渡河来引发的,所以合理的渡河操作就成了通常所说的算符,根据题目要求,可以得出以下5个算符(按照渡船方向的不同,也可以理解为10个算符):
渡1野人、渡1牧师、渡1野人1牧师、渡2野人、渡2牧师
算符知道以后,剩下的核心问题就是搜索方法了,本文采用深度优先搜索,通过一个FindNext(…)函数找出下一步可以进行的渡河操作中的最优操作,如果没有找到则返回其父节点,看看是否有其它兄弟节点可以扩展,然后用Process(…)函数递规调用FindNext(…),一级一级的向后扩展。
搜索中采用的一些规则如下:
1、渡船优先规则:甲岸一次运走的人越多越好(即甲岸运多人优先),同时野人优先运走;
乙岸一次运走的人越少越好(即乙岸运少人优先),同时牧师优先运走;
2、不能重复上次渡船操作(通过链表中前一操作比较),避免进入死循环;
3、任何时候河两边的野人和牧师数均分别大于等于0且小于等于3;
4、由于只是找出最优解,所以当找到某一算符(当前最优先的)满足操作条件后,不再搜索其兄弟节点,而是直接载入链表。
5、若扩展某节点a的时候,没有找到合适的子节点,则从链表中返回节点a的父节点b,从上次已经选择了的算符之后的算符中找最优先的算符继续扩展b。

游过去

野人过河新问题 算法~

野人过河问题算法分析
野人过河问题算法分析


野人过河问题属于人工智能学科中的一个经典问题,问题描述如下: 有三个牧师(也有的翻译为传教士)和三个野人过河,只有一条能装下两个人的船,在河的任何一方或者船上,如果野人的人数大于牧师的人数,那么牧师就会有危险. 你能不能找出一种安全的渡河方法呢?
一、算法分析
先来看看问题的初始状态和目标状态,假设和分为甲岸和乙岸:
初始状态:甲岸,3野人,3牧师;
乙岸,0野人,0牧师;
船停在甲岸,船上有0个人;
目标状态:甲岸,0野人,0牧师;
乙岸,3野人,3牧师;
船停在乙岸,船上有0个人;
整个问题就抽象成了怎样从初始状态经中间的一系列状态达到目标状态。问题状态的改变是通过划船渡河来引发的,所以合理的渡河操作就成了通常所说的算符,根据题目要求,可以得出以下5个算符(按照渡船方向的不同,也可以理解为10个算符):
渡1野人、渡1牧师、渡1野人1牧师、渡2野人、渡2牧师
算符知道以后,剩下的核心问题就是搜索方法了,本文采用深度优先搜索,通过一个FindNext(…)函数找出下一步可以进行的渡河操作中的最优操作,如果没有找到则返回其父节点,看看是否有其它兄弟节点可以扩展,然后用Process(…)函数递规调用FindNext(…),一级一级的向后扩展。
搜索中采用的一些规则如下:
1、渡船优先规则:甲岸一次运走的人越多越好(即甲岸运多人优先),同时野人优先运走;
乙岸一次运走的人越少越好(即乙岸运少人优先),同时牧师优先运走;
2、不能重复上次渡船操作(通过链表中前一操作比较),避免进入死循环;
3、任何时候河两边的野人和牧师数均分别大于等于0且小于等于3;
4、由于只是找出最优解,所以当找到某一算符(当前最优先的)满足操作条件后,不再搜索其兄弟节点,而是直接载入链表。
5、若扩展某节点a的时候,没有找到合适的子节点,则从链表中返回节点a的父节点b,从上次已经选择了的算符之后的算符中找最优先的算符继续扩展b。


二、基本数据结构
仔细阅读问题,可以发现有些基本东西我们必须把握,例如:每时刻河两岸野人牧师各自的数目、船的状态、整个问题状态。所以我们定义如下几个数据结构:
typedef struct _riverside // 岸边状态类型
{
int wildMan; // 野人数
int churchMan; // 牧师数
}RIVERSIDE;
typedef struct _boat // 船的状态类型
{
int wildMan; // 野人数
int churchMan; // 牧师数
}BOAT;
typedef struct _question // 整个问题状态
{
RIVERSIDE riverSide1; // 甲岸
RIVERSIDE riverSide2; // 乙岸
int side; // 船的位置, 甲岸为-1, 乙岸为1
BOAT boat; // 船的状态
_question* pPrev; // 指向前一渡船操作
_question* pNext; // 指向后一渡船操作
}QUESTION;
用QUESTION来声明一个最基本的链表。


三、程序流程及具体设计
本文只找出了最优解,据我所知,本问题有三种解。如果真要找出这三种解,^_^,那就得加强对链表的操作了,比如说自己写一个动态链表类,顺便完成一些初始化工作,估计看起来会舒服些。
下面给出部分关键程序:
// 主函数
int main()
{
// 初始化
QUESTION* pHead = new QUESTION;
pHead->riverSide1.wildMan = 3;
pHead->riverSide1.churchMan = 3;
pHead->riverSide2.wildMan = 0;
pHead->riverSide2.churchMan = 0;
pHead->side = -1; // 船在甲岸
pHead->pPrev = NULL;
pHead->pNext = NULL;
pHead->boat.wildMan = 0;
pHead->boat.churchMan = 0;
if (Process(pHead))
{
// ......... 遍历链表输出结果
}
cout<<"成功渡河。";
}
else
cout<<"到底怎样才能渡河呢? 郁闷!"<<endl;
// 回收内存空间
while (pHead)
{
QUESTION* pTemp = pHead->pNext;
delete pHead;
pHead=pTemp;
}
pHead = NULL;
return 0;
}

// 渡船过程, 递规调用函数FindNext(...)

BOOL Process(QUESTION* pQuest)
{
if (FindNext(pQuest))
{
QUESTION* pNew = new QUESTION;
pNew->riverSide1.wildMan = pQuest->riverSide1.wildMan + pQuest->boat.wildMan*(pQuest->side);
pNew->riverSide1.churchMan = pQuest->riverSide1.churchMan + pQuest->boat.churchMan*(pQuest->side);
pNew->riverSide2.wildMan = 3 - pNew->riverSide1.wildMan;
pNew->riverSide2.churchMan = 3 - pNew->riverSide1.churchMan;
pNew->side = (-1)*pQuest->side;
pNew->pPrev = pQuest;
pNew->pNext = NULL;
pNew->boat.wildMan = 0;
pNew->boat.churchMan = 0;
pQuest->pNext = pNew;
if (pNew->riverSide2.wildMan==3 && pNew->riverSide2.churchMan==3)
return TRUE; // 完成
return Process(pNew);
}
else
{
QUESTION* pPrev = pQuest->pPrev;
if (pPrev == NULL)
return FALSE; // 无解
delete pQuest;
pPrev->pNext = NULL;
return Process(pPrev); // 返回其父节点重新再找
}
return TRUE;
}

// 判断有否下一个渡河操作, 即根据比较算符找出可以接近目标节点的操作
// 算符共5个: 1野/ 1牧 / 1野1牧 / 2野 / 2牧
BOOL FindNext(QUESTION* pQuest)
{
// 基本规则
// 渡船的优先顺序: 甲岸运多人优先, 野人优先; 乙岸运少人优先, 牧师优先.
static BOAT boatState[5]; // 5个算符
if (pQuest->side == -1)
{
boatState[0].wildMan = 2;
boatState[0].churchMan = 0;
boatState[1].wildMan = 1;
boatState[1].churchMan = 1;
boatState[2].wildMan = 0;
boatState[2].churchMan = 2;
boatState[3].wildMan = 1;
boatState[3].churchMan = 0;
boatState[4].wildMan = 0;
boatState[4].churchMan = 1;
}
else
{
boatState[0].wildMan = 0;
boatState[0].churchMan = 1;
boatState[1].wildMan = 1;
boatState[1].churchMan = 0;
boatState[2].wildMan = 0;
boatState[2].churchMan = 2;
boatState[3].wildMan = 1;
boatState[3].churchMan = 1;
boatState[4].wildMan = 2;
boatState[4].churchMan = 0;
}
int i; // 用来控制算符
if (pQuest->boat.wildMan == 0 && pQuest->boat.churchMan == 0) // 初始状态, 第一次渡河时
i = 0; // 取算符1
else
{
for (i=0; i<5; i++) // 扩展同一节点时, 已经用过的算符不再用, 按优先级来
if (pQuest->boat.wildMan == boatState[i].wildMan && pQuest->boat.churchMan == boatState[i].churchMan)
break;
i++;
}
if (i < 5)
{
int j;
for (j=i; j<5; j++)
{
int nWildMan1 = pQuest->riverSide1.wildMan + boatState[j].wildMan * pQuest->side;
int nChurchMan1 = pQuest->riverSide1.churchMan + boatState[j].churchMan * pQuest->side;
int nWildMan2 = 3 - nWildMan1;
int nChurchMan2 = 3 - nChurchMan1;
// 判断本次操作的安全性, 即牧师数量>=野人或牧师数为0
if ((nWildMan1 <= nChurchMan1 || nChurchMan1 == 0) &&
(nWildMan2 <= nChurchMan2 || nChurchMan2 == 0) &&
nWildMan1 >=0 && nChurchMan1 >=0 && nWildMan2 >=0 && nChurchMan2 >= 0)
{
// 本操作是否重复上次操作,注意方向不同
if (pQuest->pPrev != NULL)
{
if (pQuest->pPrev->boat.wildMan == boatState[j].wildMan &&
pQuest->pPrev->boat.churchMan == boatState[j].churchMan)
continue;
}
break; // 该操作可行, 推出循环,只找出当前最优节点
}
}
if (j < 5)
{
pQuest->boat.wildMan = boatState[j].wildMan;
pQuest->boat.churchMan = boatState[j].churchMan;
return TRUE;
}
}
return FALSE;

}

本来没打算发出来。送你算了。
下面的程序称得上perfect,程序在dev-c++下编译通过.
#include
#include
#include

#define maxloop 100 /* 最大层数,对于不同的扩展方法自动调整取值 */
#define pristnum 3 /*初始化时设定有3个野人3个传教士,实际可以改动*/
#define slavenum 3

struct SPQ{ int sr,pr; /* 船运行一个来回后河右岸的野人、传教士的人数 */
int sl,pl; /* 船运行一个来回后河左岸的野人、传教士的人数 */
int ssr,spr; /* 回来(由左向右时)船上的人数 */
int sst,spt; /* 去时(由右向左时)船上的人数 */
int loop; /* 本结点所在的层数 */
struct SPQ *upnode ,*nextnode;/* 本结点的父结点和同层的下一个结点的地址 */
}spq;
int loopnum;/* 记录总的扩展次数 */
int openednum;/* 记录已扩展节点个数 */
int unopenednum;/* 记录待扩展节点个数 */
int resultnum;
struct SPQ *opened;
struct SPQ *oend;
struct SPQ *unopened;
struct SPQ *uend;
struct SPQ *result;
void initiate();
void releasemem();
void showresult();
void addtoopened(struct SPQ *ntx);
int search();
void goon();
int stretch(struct SPQ* ntx);
void recorder();

int main()
{
int flag; /* 标记扩展是否成功 */
for( ; ; )
{
initiate();
flag = search ();
if(flag == 1)
{
recorder();
releasemem();
showresult();
goon();
}
else
{
printf("无法找到符合条件的解");
releasemem();
goon();
}
}

system("pause");
return 0;
}

void initiate()
{
int x;
char choice;
uend = unopened = (struct SPQ*)malloc(sizeof(spq));
if(uend==NULL)
{
printf("
内存不够!
");
exit(0);
}
unopenednum=1;
openednum=0;
unopened -> upnode = unopened; /* 保存父结点的地址以成链表 */
unopened -> nextnode = unopened;
unopened -> sr = slavenum;
unopened -> pr = pristnum;
unopened -> sl = 0;
unopened -> pl = 0;
unopened -> sst = 0;
unopened -> spt = 0;
unopened -> ssr = 0;
unopened -> spr = 0;
unopened -> loop = 0;
printf("题目:设有n个传教士和m个野人来到河边,打算乘一只船从右岸到左岸去。
");
printf("该船的负载能力为两人。在任何时候,如果野人人数超过传教士人数,野人
");
printf("就会把传教士吃掉。他们怎样才能用这条船安全的把所有人都渡过河去?
");
printf("
默认的n、m值皆为3
");
for(;;)
{
printf("
是否修改?(Y/N)");
scanf("%s",&choice);
choice=toupper(choice);
if(choice=='Y')
{
printf("
请输入传教士人数");
for(;;)
{
scanf("%d",&x);
if(x>0)
{
unopened -> pr = x;
break;
}
else printf("
输入值应大于0!
请重新输入");
}
printf("
请输入野人人数");
for(;;)
{
scanf("%d",&x);
if(x>0)
{
unopened -> sr = x;
break;
}
else printf("
输入值应大于0!
请重新输入");
}
break;
}
if(choice=='N')break;
}

}
int search()
{
int flag;
struct SPQ *ntx; /* 提供将要扩展的结点的指针 */
for( ; ; )
{
ntx = unopened; /* 从待扩展链表中提取最前面的一个 */
if(ntx->loop == maxloop)
return 0;
addtoopened(ntx); /* 将ntx加入已扩展链表,并将这个节点从待扩展链表中去掉 */
flag = stretch(ntx); /* 对ntx进行扩展,返回-1,0,1 */
if(flag == 1)
return 1;
}
}
int stretch(struct SPQ *ntx)
{
int fsr , fpr ; /* 在右岸上的人数 */
int fsl , fpl ; /* 在左岸上的人数 */
int sst , spt ; /* 出发时在船上的人数 */
int ssr , spr ; /* 返回时船上的人数 */
struct SPQ *newnode;
for (sst = 0 ; sst <= 2 ; sst++) /* 讨论不同的可能性并判断是否符合条件 */
{
fsr = ntx -> sr;
fpr = ntx -> pr;
fsl = ntx -> sl;
fpl = ntx -> pl;
if ((sst <= fsr) && (( 2 - sst) <= fpr))/* 满足人数限制 */
{
spt = 2 - sst;
fsr = fsr - sst;
fpr = fpr - spt;
if((fpr == 0) && (fsr == 0))/* 搜索成功 */
{
newnode = (struct SPQ*) malloc (sizeof(spq));
if(newnode==NULL)
{
printf("
内存不够!
");
exit(0);
}
newnode -> upnode = ntx; /* 保存父结点的地址以成链表 */
newnode -> nextnode = NULL;
newnode -> sr = 0;
newnode -> pr = 0;
newnode -> sl = opened -> sr;
newnode -> pl = opened -> pr;
newnode -> sst = sst;
newnode -> spt = spt;
newnode -> ssr = 0;
newnode -> spr = 0;
newnode -> loop = ntx -> loop + 1;
oend -> nextnode = newnode;
oend = newnode;
openednum++;
return 1;
}
else if ((fpr - fsr) * fpr >= 0) /* 判断是否满足传教士人数必须大于或等于野人人数 */
{
fsl = fsl + sst;
fpl = fpl + spt;
for (ssr = 0 ; ssr <= 1 ; ssr++) /* 返回 */
{
int ffsl , ffpl;
if ((ssr <= fsl) && ((1 - ssr) <= fpl))
{
spr = 1 - ssr;
ffsl = fsl - ssr;
ffpl = fpl - spr;
if ((ffpl - ffsl) * ffpl >= 0)
{ /* 若符合条件则分配内存并付值 */
int ffsr , ffpr;
ffsr = fsr + ssr;
ffpr = fpr + spr;
newnode = (struct SPQ*) malloc (sizeof(spq));
if(newnode==NULL)
{
printf("
内存不够!
");
exit(0);
}
newnode -> upnode = ntx; /* 保存父结点的地址以成链表 */
newnode -> sr = ffsr;
newnode -> pr = ffpr;
newnode -> sl = ffsl;
newnode -> pl = ffpl;
newnode -> sst = sst;
newnode -> spt = spt;
newnode -> ssr = ssr;
newnode -> spr = spr;
newnode -> loop = ntx -> loop + 1;
uend -> nextnode = newnode;
uend = newnode;
unopenednum++;

}
}
}
}
}
}
return 0;
}
void addtoopened(struct SPQ *ntx)
{
unopened = unopened -> nextnode;
unopenednum--;
if (openednum == 0 )
oend = opened = ntx;
oend -> nextnode = ntx;
oend = ntx;
openednum++;
}
void recorder()
{
int i , loop;
struct SPQ *newnode;
struct SPQ *ntx;
loop = oend -> loop;
ntx = oend;
resultnum = 0;
for( i = 0 ; i <= loop ; i++ )
{
newnode = (struct SPQ*) malloc (sizeof(spq));
if(newnode==NULL)
{
printf("
内存不够!
");
exit(0);
}
newnode -> sr = ntx -> sr;
newnode -> pr = ntx -> pr;
newnode -> sl = ntx -> sl;
newnode -> pl = ntx -> pl;
newnode -> sst = ntx -> sst;
newnode -> spt = ntx -> spt;
newnode -> ssr = ntx -> ssr;
newnode -> spr = ntx -> spr;
newnode -> nextnode = NULL;
ntx = ntx -> upnode;
if(i == 0)
result = newnode;
newnode -> nextnode = result;
result = newnode;
resultnum++;
}
}
void releasemem()
{
int i;
struct SPQ* nodefree;
for ( i = 1 ; i < openednum ; i++ )
{
nodefree = opened;
opened = opened -> nextnode;
free(nodefree);
}
for ( i = 0 ; i < unopenednum ; i++ )
{
nodefree = unopened;
unopened = unopened -> nextnode;
free(nodefree);
}
}
void showresult()
{
int i;
int fsr , fpr ; /* 在右岸上的人数 */
int fsl , fpl ; /* 在左岸上的人数 */
struct SPQ* nodefree;
printf("%d个传教士",result -> pr);
printf("%d个野人",result -> sr);
printf("%d个传教士",result -> pl);
printf("%d个野人",result -> sl);
for ( i = 1 ; i < resultnum ; i++ )
{
nodefree = result;
result = result -> nextnode;
free(nodefree);
printf("

左岸人数 船上人数及方向 右岸人数
");
printf("第%d轮
",i);
fpl = result -> pl - result -> spt + result -> spr;
fpr = result -> pr - result -> spr;
fsl = result -> sl - result -> sst + result -> ssr;
fsr = result -> sr - result -> ssr;
printf("传教士%8d%8d spt,fpr);
printf("野 人%8d%8d sst,fsr);
printf("传教士%8d%8d->%8d
",result -> pl,result -> spr,result -> pr - result -> spr);
printf("野 人%8d%8d->%8d
",result -> sl,result -> ssr,result -> sr - result -> ssr);
}
printf("
全体传教士和野人全部到达对岸");
free(result);
}
void goon()
{
char choice;
for(;;)
{
printf("是否继续?(Y/N)
");
scanf ("%s" , &choice);
choice=toupper(choice);
if(choice=='Y')break;
if(choice=='N')exit(0);
}
}

有4个传教士和4个野人同时要过河,但是只有1条无人驾驶的小船,小船一 ...
答:首先 让一个传教士和一个野人过河 传教士回来 之后 2个传教士和一个野人过河 然后一个传教士和一个野人回来 然后 3个传教士过河 一个野人回来 之后3个野人过河 一个传教士回去接那个野人 就行了

3个文明人3个野人要过河,有一只船,来回间必须要有人乘船,问如何使他们...
答:试一下 R表示文明人 G表示野蛮人 奇数行表示左右岸状态 上船表示出发,回来表示回来 RRRGGG RG上船 R回来 RRRGG G GG上船 G回来 RRRG GG RR上船 RG回来 RRGG RG RR上船 G回来 GGG RRR GG上船 G回来 GG上船 OK

有三个牧师和三个野人过河,只有一条能装下两个人的船,至少需要一撑船...
答:这是个逻辑推理游戏,网络上可以找到过关的方法和步骤的。祝你好运。类似的这种游戏还有好多关,都挺有意思的。

人工智能野人传教士过河
答:三对三有解。我用 Python 写了搜寻答案的程序。要知道其它组合有没有解,只要改一改 “mCOUNT, cCOUNT = 3, 3” 这一行然后运行就知道了。有空的话我会译成 Java 贴上来。'''Solve the Missionaries And Cannibals Puzzle by trying all legal moves.The puzzle imposes two constraints:1) ...

三个野人,三个神父 在河一边 要给他们送过河 有一条船一次最多_百度知...
答:野人 123 神父 456 45先走 河对岸放下4 5返回 5返回后 12走 河对岸 放下12 4返回 4返回后 45走 河对岸 放下4 5返回 5返回后 56走 河对岸 放下56 1返回 1返回后 23走 河对岸 放下2 3返回 3返回后 13走 河对岸 放下13 全部运到 ...

急求 人工智能 野人传教士过河问题,详解!!!
答:0)(3 3 1) (3 3 0)达不到 (3)2个野人去,1个野人回 2个野人去,1个野人回 2个传教士去,1个野人与1个传教士回 2个传教士去,1个野人回 2个野人去,1个野人回 2个野人去,完成 不合法的状态和重复状态,我都没画出,你可以自己加一下,也可以结合图 说明一下 ...

智力趣味题
答:1.一道士和一野人先过,野人上岸,2.道士回再接一道士到对岸,上岸一道士 3.一道士回再接一道士,然后到对岸,二道士上岸,这样对岸就有三道士,4.野人回二次接剩下的二野人OK

用java实现野人传教士过河问题
答:n); for(Node node : resultList) { System.out.println("左岸传教士:"+node.getLeftPeo()+"左岸野人: "+node.getLeftSavage()+" 右岸传教士: "+node.getRightPeo()+"右岸野人:"+node.getRightSavage()+"船上传教士:"+node.getOnBoatPeoNum()+"船上野人:"+node.getOnBoatSa...

三个和尚和三个野人过河,他们只有一条小船,每次只能乘坐两个人,每次两...
答:在两野人过去。在一野人回来。对面有两野人。这边有三和尚一野人。在两和尚过去。一和尚一野人回来。对面一和尚一野人。这边两和尚两野人。最后两和尚过去。一野人回来。对面三和尚。这边三野人。剩下的就三个野人二个过去一个回来在接另外个就好啦 ...

森林最后那个球有什么用
答:说起过河大家第一个想到应该是游泳,不过游戏中不仅仅是在爬山时需要耗费体力,在游泳时同样也会耗费体力,面对游戏中大量宽敞的河面,你那微薄的体力基本上都难以横渡,往往是才游到一半就体力不支而溺水。不过过河的方法其实也有很多种:可以找到带帆的木排使用树上掉的芭蕉叶来扇动船帆过河。使用冰冻...