原文地址:http://read.pudn.com/downloads37/sourcecode/math/118734/%E9%87%8E%E4%BA%BA%E4%BC%A0%E6%95%99%E5%A3%AB%E9%97%AE%E9%A2%98.doc
传教士和野人渡河问题
刘宪国 050422023
野人过河问题描述如下:有三个传教士和三个野人过河,只有一条能装下两个人的船,在河的任何一方或者船上,如果野人的人数大于传教士的人数,那么传教士就会有危险.
一、算法分析
先来看看问题的初始状态和目标状态,假设分为甲岸和乙岸:
初始状态:甲岸,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。
二、基本数据结构
定义如下几个数据结构:
typedefstruct _riverside{ //岸边状态类型
intwildMan; //野人数
intchurchMan; //传教士数
}RIVERSIDE;
typedefstruct _boat{ //船的状态类型
intwildMan; //野人数
intchurchMan; //传教士数
}BOAT;
typedefstruct _question{ //整个问题状态
RIVERSIDEriverSide1; //甲岸
RIVERSIDEriverSide2; //乙岸
intside; //船的位置,甲岸为-1,乙岸为1
BOATboat; //船的状态
_question*pPrev; //指向前一渡船操作
_question*pNext; //指向后一渡船操作
}QUESTION;
用QUESTION来声明一个最基本的链表。
三、程序流程及具体设计
下面给出部分关键程序:
intmain()//主函数
{//初始化
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<<"到底怎样才能渡河呢?郁闷!"
<pNext;
deletepHead;
pHead=pTemp;
}
pHead= NULL;
return0;
}
//渡船过程,递规调用函数FindNext(...)
BOOLProcess(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; //完成
returnProcess(pNew);
}
else
{
QUESTION*pPrev = pQuest->pPrev;
if(pPrev == NULL) return FALSE; //无解
deletepQuest;
pPrev->pNext= NULL;
returnProcess(pPrev); //返回其父节点重新再找
}
returnTRUE;
}
//判断有否下一个渡河操作,即根据比较算符找出可以接近目标节点的操作
//算符共5个:1野/1牧/1野1牧/2野/2牧
BOOLFindNext(QUESTION* pQuest)
{//基本规则
//渡船的优先顺序:甲岸运多人优先,野人优先;乙岸运少人优先,传教士优先.
staticBOAT 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;
}
inti; //用来控制算符
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)
{
intj;
for(j=i; j<5; j++)
{
intnWildMan1 = pQuest->riverSide1.wildMan + boatState[j].wildMan *pQuest->side;
intnChurchMan1 = pQuest->riverSide1.churchMan +boatState[j].churchMan * pQuest->side;
intnWildMan2 = 3 - nWildMan1;
intnChurchMan2 = 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;
returnTRUE;
}
}
returnFALSE;
}
四.结果
-----河甲岸-------|-------河乙岸--------|--船行方向--|------移动人数-----
野人传教士 野人传教士 野人传教士
3 3 0 0甲->乙2 0
1 3 2 0乙->甲1 0
2 3 1 0甲->乙2 0
0 3 3 0乙->甲1 0
1 3 2 0甲->乙0 2
1 1 2 2乙->甲1 1
2 2 1 1甲->乙0 2
2 0 1 3乙->甲1 0
3 0 0 3甲->乙2 0
1 0 2 3乙->甲0 1
1 1 2 2甲->乙1 1
0 0 3 3
成功渡河!