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;
}