实验一 知识表示方法.docx
《实验一 知识表示方法.docx》由会员分享,可在线阅读,更多相关《实验一 知识表示方法.docx(16页珍藏版)》请在冰豆网上搜索。
![实验一 知识表示方法.docx](https://file1.bdocx.com/fileroot1/2022-12/13/89e7bda4-e698-4c2e-a6b9-2baa70a4a748/89e7bda4-e698-4c2e-a6b9-2baa70a4a7481.gif)
实验一知识表示方法
实验一:
知识表示方法
一、实验目的
状态空间表示法是人工智能领域最基本的知识表示方法之一,也是进一步学习状态空间搜索策略的基础,本实验通过牧师与野人渡河的问题,强化学生对知识表示的了解和应用,为人工智能后续环节的课程奠定基础。
二、问题描述
有n个牧师和n个野人准备渡河,但只有一条能容纳c个人的小船,为了防止野人侵犯牧师,要求无论在何处,牧师的人数不得少于野人的人数(除非牧师人数为0),且假定野人与牧师都会划船,试设计一个算法,确定他们能否渡过河去,若能,则给出小船来回次数最少的最佳方案。
三、基本要求
输入:
牧师人数(即野人人数):
n;小船一次最多载人量:
c。
输出:
若问题无解,则显示Failed,否则,显示Successed输出一组最佳方案。
用三元组(X1,X2,X3)表示渡河过程中的状态。
并用箭头连接相邻状态以表示迁移过程:
初始状态->中间状态->目标状态。
例:
当输入n=2,c=2时,输出:
221->110->211->010->021->000
其中:
X1表示起始岸上的牧师人数;X2表示起始岸上的野人人数;X3表示小船现在位置(1表示起始岸,0表示目的岸)。
要求:
写出算法的设计思想和源程序,并以图形用户界面实现人机交互,进行输入和输出结果,如:
Pleaseinputn:
2Pleaseinputc:
2
SuccessedorFailed?
:
Successed
OptimalProcedure:
221->110->211->010->021->000
四、实验组织运行要求
本实验采用集中授课形式,每个同学独立完成上述实验要求。
五、实验条件
每人一台计算机独立完成实验。
#include
#include
#include
typedefstruct
{
intxds;//xiudaoshi
intyr;//yeren
intcw;//chuanwei
}DataType;
DataTypefa[50000];
typedefstructnode
{
DataTypedata;
structnode*son;
structnode*bro;
structnode*par;
structnode*next;
}Ltable;
voidLtableinit(Ltable**head)//初始化邻接表的操作
{
*head=(Ltable*)malloc(sizeof(Ltable));//动态分配空间
(*head)->son=NULL;
(*head)->bro=NULL;
(*head)->par=NULL;
(*head)->next=NULL;
}
voidinsertson(Ltable*head,DataTypex)//在邻接表中插入儿子结点的操作
{
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;
}
voidinsertbro(Ltable*head,DataTypex)//在邻接表中插入兄弟结点的操作,所有的兄弟结点都指向他们右边的结点;
{
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;
}
intfindfa(DataTypex,intn)//生成在船上修道士仍安全的几种情况;
{
inti=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;
}
}
returni;
}
intjiancha(DataTypex,intn)//安全性检测,检查当前情况下,修道士是否安全
{
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)
return1;
else
return0;
}
voidprint(Ltable*q,Ltable*p)//打印安全渡河的过程
{
DataTypea[100];
inti=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%d0)\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%d1)\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);
}
elseprintf("\n");
}
printf("渡河成功!
\n");
}
voidwork(Ltable*p,intn,intc)
{
Ltable*q,*t;
DataTypetem;
inti,flag,flag1,g=0,j,count=0;
q=p->son;
while(q!
=NULL)
{
flag=0;
j=findfa(q->data,c);
for(i=0;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);
}
intmain()
{
Ltable*p;
DataTypetem;
Ltableinit(&p);//初始化邻接表;
intn,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);//进行广度搜索;
}
return1;
}
#include
#include
#include
typedefstruct
{
intxds;/*xiudaoshi*/
intyr;/*yeren*/
intcw;/*船工*/
}DataType;
DataTypefa[5000];
typedefstructnode
{
DataTypedata;
structnode*son;
structnode*bro;
structnode*par;
structnode*next;
}Ltable;
voidprint(Ltable*q,Ltable*p);
voidwork(Ltable*p,intn,intc);
voidinsertbro(Ltable*head,DataTypex);
voidLtableinit(Ltable**head);
intmain(void)
{
Ltable*p;
DataTypetem;
intn,c;
Ltableinit(&p);/*初始化邻接表;*/
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);/*进行广度搜索;*/
}
return0;
}
voidLtableinit(Ltable**head)/*初始化邻接表的操作*/
{
if((*head=(Ltable*)malloc(sizeof(Ltable)))==NULL);/*动态分配空间*/
{
puts("Mallocerror!
");
exit
(1);
}
(*head)->son=NULL;
(*head)->bro=NULL;
(*head)->par=NULL;
(*head)->next=NULL;
}
voidinsertbro(Ltable*head,DataTypex)/*在邻接表中插入兄弟结点的操作,所有的兄弟结点都指向他们右边的结点;*/
{
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;
}
voidwork(Ltable*p,intn,intc)
{
Ltable*q,*t;
DataTypetem;
inti,flag,flag1,g=0,j,count=0;
q=p->son;
while(q!
=NULL)
{
flag=0;
j=findfa(q->data,c);
for(i=0;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);
}
voidprint(Ltable*q,Ltable*p)/*打印安全渡河的过程*/
{
DataTypea[100];
inti=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%d0)\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%d1)\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);
}
elseprintf("\n");
}
printf("渡河成功!
\n");
}
野人过河问题算法分析
野人过河问题属于人工智能学科中的一个经典问题,问题描述如下:
有三个牧师(也有的翻译为传教士)和三个野人过河,只有一条能装下两个人的船,在河的任何一方或者船上,如果野人的人数大于牧师的人数,那么牧师就会有危险. 你能不能找出一种安全的渡河方法呢?
一、算法分析
先来看看问题的初始状态和目标状态,假设和分为甲岸和乙岸:
初始状态:
甲岸,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来声明一个最基本的链表。
作者:
dick990333 发表时间:
2008-9-221:
37:
00
第5楼
三、程序流程及具体设计
本文只找出了最优解,据我所知,本问题有三种解。
如果真要找出这三种解,^_^,那就得加强对链表的操作了,比如说自己写一个动态链表类,顺便完成一些初始化工作,估计看起来会舒服些。
下面给出部分关键程序:
// 主函数
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<<"到底怎样才能渡河呢?
郁闷!
"< // 回收内存空间
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->riverSide