1、问题描述:这是一个古典问题.假设有n个道士和n个野人准备渡河.但只有一条能容纳c人的小船,为了防止野人侵犯修道士,要求无论在何处,修道士的个数不得少于野人的个数(除非修道士个数为0).如果两种人都回会划船,设计一个算法,确定他们能否过河.若能,则给出小船来回次数最少的最佳方案.
2、设计
2.1 设计思想
(1)存储结构
typedef struct
{
int xds; //修道士
int yr; //野人
int cw; //船的位置
}DataType; //表示当前状态的结构体
typedef struct node
{
DataType data;
struct node *son;
struct node *bro;
struct node *par;
struct node *next;
}Ltable; //定义的邻接表
(2)主要算法的基本思想
1.用一个三元组(x1,x2,x3)表示渡河过程中各个状态。x1表示起始岸上修道士的个数,x2为起始岸上野人个数,x3表示小船位置(0--在目的岸上,1--在起始岸上)。
2.根据给出的小船上的位置数量,生成小船上的安全状态,即在船上的时候修道士的人数也要比野人的数量要多(除非修道士人数为0)。渡船优先规则:起始岸一次运走的人越多越好(即起始岸运多人优先),同时野人优先运走;目的岸一次运走的人越少越好(即目的岸运少人优先),同时修道士优先运走;
3.累似于操作系统中的银行家算法的安全性检测,即让修道士跟野人上船后,检测当船到达对岸后,两岸修道士的安全状态,若修道士安全,则将此结点加入到邻接表中。
4..采用广度搜索,得到首先搜索到的边数最少的一条通路。
5.若问题有解,输出一个最佳方案。无解给出失败信息。
2.2设计表示法
(1)过程或函数调用关系
main()->Ltableinit()->insertson()->work() ;
work()->findfa()->jiancha ()->insertson()->insertbro()->print() ;
(2)基于数据结构的操作组
Ltableinit() , insertson() , insertbro() ;
(3) 过程或函数接口规格说明
void Ltableinit(Ltable **head) //初始化邻接表
void insertson(Ltable *head, DataType x) //将元素x作为儿子结点插入到邻接表
void insertbro(Ltable *head,DataType x) //将元素x作为兄弟结点插入到邻接表
int findfa(DataType x,int n) //生成船上的修道士安全的状态
int jiancha(DataType x,int n) //安全性检测,检测此时刻,两岸修道士是否安全
void print(Ltable *q,Ltable *p) //打印路径
2.3 详细设计
void work(Ltable *p,int n,int c)
{
Ltable *q,*t;
DataType tem;
int i,flag,flag1,g=0,j,count=0;
q=p->son;
while (q!=NULL)
{
flag=0;
j=findfa(q->data,c);
for (i=0;i<j;i++)
{
tem.xds=q->data.xds-fa[i].xds;
tem.yr=q->data.yr-fa[i].yr;
tem.cw=1-q->data.cw;
t=q;
if (jiancha (tem,n))
{
flag1=1;
while (t!=p)
{
if(tem.xds== t->data.xds&&tem.yr==t->data.yr&&tem.cw==t->data.cw)
{
flag1=0;
break;
}
t=t->par;
}
if(flag1==1)
{
if (flag==0)
{
insertson(q, tem);
flag=1;
}
else
insertbro(q,tem);
if (tem.xds==0&&tem.yr==0&&tem.cw==0)
{
print(q,p);
count++;
}
}
}
}
q=q->next;
}
if (count==0)
printf("无法成功渡河,修道士好郁闷!/n");
else
printf("有%d种渡河方式。/n",count);
}
3.调试分析
(1)在刚开始做的时候,建立邻接表这部分感觉有点困难,直到现在还感觉自己建的图更像是树,由于要建成邻接表,于是我设了三个指针,son(儿子指针),bro(兄弟指针),par(双亲指针),层与层之间是用son指针建立连接的,同一层的元素用bro指针建立连接,同时每一个结点都指向上一层它们的根结点(父亲结点)。
(2)在执行的时候发现存在死循环,通过断点跟踪后,发现会存在一状态跟之前有过的状态重复,从而就导致了死循环的产生,找到原因后,我把这种情况处理了一个,解决了这个问题。
4.用户手册: 本程序运行环境为DOS,执行文件为:yeren.exe.
进入演示程序后,即出现提示信息:请输入修道士与野人的人数n:请输入船可容纳的人数c:输入后程序执行相应操作后,显示相应结果。
5.源代码
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
typedef struct
{
int xds; //xiudaoshi
int yr; //yeren
int cw; //chuanwei
}DataType;
DataType fa[50000];
typedef struct node
{
DataType data;
struct node *son;
struct node *bro;
struct node *par;
struct node *next;
}Ltable;
void Ltableinit(Ltable **head) //初始化邻接表的操作
{
*head=(Ltable *)malloc(sizeof (Ltable)); //动态分配空间
(*head)->son=NULL;
(*head)->bro=NULL;
(*head)->par=NULL;
(*head)->next=NULL;
}
void insertson(Ltable *head, DataType x) //在邻接表中插入儿子结点的操作
{
Ltable *q,*s;
q=(Ltable *)malloc(sizeof (Ltable));
q->data=x;
head->son=q;
s=head;
while (s->next!=NULL)
s=s->next;
q->par=head;
q->son=NULL;
q->bro=NULL;
s->next=q;
q->next=NULL;
}
void insertbro(Ltable *head,DataType x) //在邻接表中插入兄弟结点的操作,所有的兄弟结点都指向他们右边的结点;
{
Ltable *q,*s;
q=(Ltable *)malloc(sizeof (Ltable));
s=head->son;
q->data=x;
while (s->bro!=NULL)
s=s->bro;
s->bro=q;
s->next=q;
q->next=NULL;
q->bro=NULL;
q->par=head;
q->son=NULL;
}
int findfa(DataType x,int n) //生成在船上修道士仍安全的几种情况;
{
int i=0,a,b,t=0;
if(x.cw)
{
a=0;b=n-a;
while (a+b>=1)
{
t++;
while (b>=0)
{
fa[i].xds=a;
fa[i].yr=b;
i++;
a++;
b--;
}
a=0;
b=n-a-t;
}
}
else
{
a=1;b=0;t=0;
while (a+b<=n)
{
t++;
while (a>=0)
{
fa[i].xds=a*(-1);
fa[i].yr=b*(-1);
i++;
a--;
b++;
}
a=fa[0].xds*(-1)+t;
b=0;
}
}
return i;
}
int jiancha(DataType x,int n) //安全性检测,检查当前情况下,修道士是否安全
{
if ((x.xds>=x.yr||x.xds==0)&&((n-x.xds)>=(n-x.yr)||x.xds==n)&&x.xds>=0&&x.xds<=n&&x.yr>=0&&x.yr<=n)
return 1;
else
return 0;
}
void print(Ltable *q,Ltable *p) //打印安全渡河的过程
{
DataType a[100];
int i=1;
a[0].cw=0;
a[0].xds=0;
a[0].yr=0;
while (q!=p)
{
a[i++]=q->data;
q=q->par;
}
while ((--i)>-1)
{
printf("( %d %d %d )",a[i].xds,a[i].yr,a[i].cw);
if (!(a[i].xds==0&&a[i].yr==0&&a[i].cw==0))
{if (a[i].cw==1)
printf(" --> ( %d %d ) --> ( %d %d 0 )/n",a[i].xds-a[i-1].xds,a[i].yr-a[i-1].yr,a[i-1].xds,a[i-1].yr);
else printf(" <-- ( %d %d ) <-- ( %d %d 1 )/n",(a[i].xds-a[i-1].xds)*(-1),(-1)*(a[i].yr-a[i-1].yr),a[i-1].xds,a[i-1].yr);
}
else printf("/n");
}
printf("渡河成功!/n");
}
void work(Ltable *p,int n,int c)
{
Ltable *q,*t;
DataType tem;
int i,flag,flag1,g=0,j,count=0;
q=p->son;
while (q!=NULL)
{
flag=0;
j=findfa(q->data,c);
for (i=0;i<j;i++)
{
tem.xds=q->data.xds-fa[i].xds;
tem.yr=q->data.yr-fa[i].yr;
tem.cw=1-q->data.cw;
t=q;
if (jiancha (tem,n))
{
flag1=1;
while (t!=p)
{
if(tem.xds==t->data.xds&&tem.yr==t->data.yr&&tem.cw==t->data.cw)
{
flag1=0;
break;
}
t=t->par;
}
if(flag1==1)
{
if (flag==0)
{
insertson(q, tem);
flag=1;
}
else
insertbro(q,tem);
if (tem.xds==0&&tem.yr==0&&tem.cw==0)
{
print(q,p);
count++;
}
}
}
}
q=q->next;
}
if (count==0)
printf("无法成功渡河,修道士好郁闷!/n");
else
printf("有%d种渡河方式。/n",count);
}
int main()
{
Ltable *p;
DataType tem;
Ltableinit(&p); //初始化邻接表;
int n,c;
while (1)
{
printf("请输入修道士与野人的人数n:/n");
scanf("%d",&n);
if (n==0)
break;
printf("请输入船可容纳的人数c:/n");
scanf("%d",&c);
tem.xds=n;
tem.yr=n;
tem.cw=1;
insertson(p, tem); //将初始状态作为头结点的孩子结点;
work(p,n,c); //进行广度搜索;
}
return 1;
}