人工智能实验指导书42+16学时Word下载.docx
《人工智能实验指导书42+16学时Word下载.docx》由会员分享,可在线阅读,更多相关《人工智能实验指导书42+16学时Word下载.docx(16页珍藏版)》请在冰豆网上搜索。
4、选择Files项,打开下拉菜单,选择Load项,选择要打开的示例程序,选择Example1示例程序,再选择Edit项,可以进行编辑源程序。
5、编辑之后,可以选择Run项,执行程序,可以在Dialog窗口进行询问,即外部目标的执行,并分析程序之功能。
6、仿前例,运行Example11、Example15,分析程序功能。
7、退出,选择Quit项,可以退出TurboProlog程序,返回到Windows2000环境。
五、源程序
Example1:
domains
person,activity=symbol
predicates
likes(person,activity)
clauses
likes(ellen,tennis).
likes(john,football).
likes(tom,baseball).
likes(eric,swimming).
likes(mark,tennis).
likes(bill,X)iflikes(tom,X).
Example11:
domains
namelist=name*
name=symbol
member(name,namelist).
member(Name,[Name|_]).
member(Name,[_|Tail])ifmember(Name,Tail).
Example15:
father(name,name)
everybody
father(leonard,katherine).
father(carl,jason).
father(carl,marilyn).
everybodyif
father(X,Y),
write(X,"
is"
Y,"
'
sfather\n"
)andfail.
实验二N!
及Fibonacci序列问题(4学时)
掌握Turboprolog软件编程使用方法;
掌握Prolog程序的各域段含义;
初步学习分析、运用递归方法解决问题。
二、实验设备
三、实验内容及步骤
一)Fibonacci序列问题描述:
数字的Fibonacci序列是个正整数序列,序列头两个数均为1,其余数为其前两个数相加得到,该序列排列如下:
1,1,2,3,5,8,13,21,34,55……
Fibonacci序列通式为:
F1=1
F2=1
Fn=Fn-1+Fn-2
二)实验内容:
1、进一步熟悉Turboprolog的运行环境,程序调试的方法;
2、初步掌握递归原理,了解问题递归的详细过程。
3、理解递归停止条件对整个递归过程的作用。
4、写出求N!
及Fibonacci序列问题的Prolog程序。
三)实验步骤:
1、启动prolog编辑环境,编辑求N!
及Fibonacci序列源程序;
2、运行程序,分析结果;
3、尝试修改程序达到同样的目的。
实验三梵塔问题(4学时)
1、掌握PROLOG编制递归程序的方法:
边界条件与递归部分的设计;
2、掌握Prolog程序的各域段含义;
3、学会分析、运用递归方法解决问题。
1、分析汉诺塔问题,找出问题本身存在的递归性,编制程序;
2、显示汉诺塔问题中圆盘的移动次序;
3、归纳出圆盘数目与移动步骤之间的数学关系;
4、分析递归问题的实质。
一)问题描述:
如上图,目的是把左边的所有盘子移到右边的杆子上。
一次只能移动一个盘子,你可以使用中间的杆子作为临时存放盘子的地方。
在移动的过程中,小盘子必须放在大盘子之上。
二)分析问题:
如果盘子只有两三个,是很容易解决的。
但是随着盘子数目的增加,问题就变得非常难解了。
事实上,已经通过数学证明过了,最少的移动次数是2
,n为盘子的数目。
最早提出这个问题的人设定的盘子数目为64,这就是说需要2
=184********709551615次移动,假设计算机每秒钟能够计算10,000,000(一千万)次,那也需要58494年,所以你千万不要试图使用此程序来解较大的数。
让我们来看看如何用递归来解决这个问题:
如果只有一个盘子,直接移过去就行了,这是递归的边界条件。
如果要移动N个盘子,就要分三步走:
1、把N-1个盘子移动到中间的杆子上(把右边的杆子作为临时存放盘子的位置)。
2、把最后一个盘子直接移到右边的杆子上。
3、最后把中间杆子上的盘子移到右边的杆子上(把左边的杆子作为临时存放盘子的位置)。
上面第一、三步用到了递归。
我们看到,通过递归把N个盘子的问题变成了两个N-1个盘子的问题。
如此下去,最后就变成了2
个一个盘子的问题了,这也就是说问题被解决了。
三)实验内容:
1、分析汉诺塔问题,找出问题本身存在的递归性。
2、分析递归原理,熟悉问题递归的详细过程。
3、编写求梵塔问题的Prolog程序。
4、显示汉诺塔问题中圆盘的移动次序。
5、更改圆盘数量,了解问题解决的递归过程。
四)实验步骤:
1、启动prolog编辑环境,编辑hanoi源程序;
3、重复比较圆盘数目,根据其求解过程得到圆盘数量与步骤数目之间的规律。
归纳并理解问题递归的实质。
实验四渡河问题(4学时)
1、进一步掌握PROLOG编制递归程序的方法;
2、理解谓词show_move、state、unsafe在程序中的作用。
1、分析该问题的实质以及其中存在的递归作用;
2、利用列表数据结构编写出渡河问题的程序;
3、联系前面的全排列问题理解列表数据结构的作用。
4.1问题的引出
一个农夫带着他的狼、山羊和白菜要过河。
河边有一只小船,但这只小船一次只能同时带两件东西(包括农夫自己,他要划船)。
如果狼和山羊留在一起.则狼要吃掉山羊;
同理如果山羊和白菜留在一起,则山羊要吃掉白菜。
农夫要如何安排渡河的步骤,才能保证农夫、山羊、狼、白菜都能安全到达河的对岸。
二)分析问题及编写程序:
这个问题还可以扩展为N1个牧师和N2个野人,而船一次可以装下M个人的情况。
我们使用Prolog解决上面的问题。
这是个典型的状态图搜索问题,所以我们首先需要解决的问题就是使用Prolog的数据结构表达两岸的状态,以及对这些状态可能的操作。
我们用下面的复合结构来表达问题的某个状态。
((左岸牧师数,左岸野人数),(右岸牧师数,右岸野人数),船的位置)
上面的结构中,船的位置为0表示船在左岸,为1表示在右岸。
一开始,所有的人都在左岸。
所以初始状态如下:
((3,3),(0,0),0)
而我们的目标状态则是:
((0,0),(3,3),1)
当然,这里只是为了方便起见,才使用了上面的结构,实际上是没有必要包括右岸的人数的,因为可以通过左岸的人数算出右岸的人数来。
不过我们这里所选用的数据结构也有其优点,它可以是程序更加容易理解。
船上所能够载人的状态就是可能的操作。
用谓词move/2表示。
move(1,0).表示船上有一位牧师,没有野人。
move(0,1).
move(0,2).
move(2,0).
move(1,1).
有了上面的表达状态的数据结构以及移动的方法,我们还需要判断状态是否合法。
下面的legal1就是完成这个任务。
legal((X,Y,_)):
-
%X为左岸状态,Y为右岸状态。
legal1(X),
%分别判断两岸的状态是否合法。
legal1(Y).
legal1((X,Y)):
X=:
=0,Y>
=0,!
.
%牧师人数为0,野人的人数大于0,合法。
Y=:
=0,X>
%野人人数为0,牧师的人数大于0,合法。
X>
=Y,X>
=0.
%牧师数大于或等于野人数,且都大于0,合法。
下面是使用legal/1的几个例子:
?
legal(((3,3),(0,0),1)).
yes
legal(((0,3),(3,0),1)).
legal(((2,3),(1,0),0)).
no
legal1只判断牧师与野人的人数是否会造成牧师受到伤害,而不判断左右岸的人数之和是否正确。
所以((2,1),(1,1),0)也是合法的状态。
不过不用担心,在我们后面的程序中会避免这种情况出现的。
下面的update/3谓词能够完成把合理的移动作用的某个状态上,从而到达新的状态。
update((X,Y,0),Move,Statu1):
%船在左岸时
(A,B)=X,
(C,D)=Y,
(E,F)=Move,
C1
is
C+E,
D1
D+F,
A1
A-E,
B1
B-F,
Statu1=((A1,B1),(C1,D1),1).
update((X,Y,1),Move,Statu1):
%船在右岸时
C-E,
D-F,
A+E,
B+F,
Statu1=((A1,B1),(C1,D1),0).
update(((3,3),(0,0),0),(1,1),X).
X
=
(2,2),(1,1),1
;
update(((0,0),(3,3),0),(1,1),X).
(-1,-1),(4,4),1
update(((1,2),(2,3),1),(3,4),X).
(4,6),(-1,-1),0
注意update只是简单地进行加减运算,它并不判断所得的新的状态是否合法。
有了以上的三个谓词move/2,update/3,legal/1我们就可以很容易的做出判断两个合法的状态相邻的谓词,当然,此谓词也可以用来寻找某个状态的相邻状态。
connect(Statu,Statu1):
move(X,Y),
update(Statu,(X,Y),Statu1),
legal(Statu1).
这是非常符合逻辑的谓词。
我们来看看功能:
connect(((3,3),(0,0),0),X).
一个野人与一个牧师过河
(3,2),(0,1),1
一个野人过河
(3,1),(0,2),1
两个野人过河
再使用典型的深度搜索方法就可以找到答案了:
findroad(X,X,L,L).%
递归的边界条件。
findroad(X,Y,L,L1):
%
L为储存的路由表。
connect(X,Z),
not(member(Z,L)),
X所连接的节点Z不在已经储存的路由表中。
findroad(Z,Y,[Z|L],L1).
findroad(((0,0),(3,3),1),((3,3),(0,0),0),[((0,0),(3,3),1)],L).
L
[((3,3),(0,0),0),
((3,1),(0,2),1),
((3,2),(0,1),0),
((3,0),(0,3),1),
((3,1),(0,2),0),
((1,1),(2,2),1),
((2,2),(1,1),0),
((0,2),(3,1),1),
((0,3),(3,0),0),
((0,1),(3,2),1),
((1,1),(2,2),0),
((0,0),(3,3),1)]
findroad/4的第三个参数是初始路由表,在此我们把终点状态放到其中,上面的findroad是从终点向起点寻找,由于是对称的,这并不影响结果。
使用广度搜索也能完成相同的任务:
findroad([],X,X).
findroad(Moves,State,Crit):
findroad(PrMoves,State,NextState),
not(member(NextState,PrMoves)),
connect(NextState,Crit),
append(PrMoves,[NextState],Moves).
findroad(L,((3,3),(0,0),0),((0,0),(3,3),1)).
L=[((3
3)
(0
0)
0),
((2
2)
(1
1)
1),
((3
((1
(2
((0
(3
0)]
好了,到此为止过河问题已经基本上解决了。
不过为了能够使用本程序分析一般情况,即牧师与野人的人数和船的载客量为其它值的情况,我们来稍微改写一下这个程序。
谓词insert_move(N),动态地向内存中加入船的载客量为N时,船上载客的所有可能情况。
我们可以试试借用谓词leagal(X,Y)来实现:
insert_move(N):
X+Y
N,
legal(X,Y),
asserta(move(X,Y)).
上面的asserta/1谓词把它的参数作为子句加入到内存中。
这个程序看似简洁,其实错了,为什么呢?
因为
X+Y
N
有无穷组解。
所以要用以下方法:
get_integer(L,H,X):
-L>
H,!
fail.
get_integer(L,H,L).
-L1
L+1,get_integer(L1,H,X).
insert_move0(N),
insert_move1(N).
insert_move0(0).
%野人或牧师有一方人数为0,则另一方的人数可以是0--N.
insert_move0(N):
asserta(move(N,0)),
asserta(move(0,N)),
N1
N-1,
insert_move0(N1).
insert_move1(N):
-%人数都不为0时,则野人的人数不能多于牧师的人数,并且总人数不能多于N.
get_integer(1,N,X),
get_integer(X,N,Y),
X+Y=<
N,
asserta(move(Y,X)),
fail.
insert_move1(_).
来试一下功能。
insert_move(3).
move(X,Y).
2
Y
1
0
3
谓词insert_statu(N1,N2),动态地加入初始状态与目标状态,当然是N个牧师与N个野人的情况。
insert_statu(N1,N2):
asserta(inistatu(((N1,N2),(0,0),0))),
asserta(desstatu(((0,0),(N1,N2),1))).
下面的谓词用来从内存中删除以上的动态信息。
el_move:
retract(move(X,Y)),
del_move.
del_stat:
retract(inistatu(X)),
retract(desstatu(Y)),!
del_stat.
这些谓词的第二个子句是为了保证谓词永远能够成功,以免再调用它们时出现问题。
最后我们再分别编写使用广度搜索与深度搜索的主程序。
widesolve(N1,N2,M):
del_move,
del_stat,
insert_move(M),
insert_statu1,(N1,N2),
inistatu(X),
desstatu(Y),
!
findroad(L,X,Y),
writelist(L),
nl.
deepsolve(N1,N2,M):
insert_statu(N1,N2),
desstatu(Y),!
findroad(Y,X,[Y],L),
第一个参数为野人与牧师的人数,第二个参数为船的最大载人量。
它们分别调用findroad/3(广度搜索)findroad/4(深度搜索)来完成搜索任务。
(在Prolog中参数数目不同的谓词,是不同的谓词)
我们在深度搜索中加入的截断,因为我们想通过deepsolve判断问题是否有解,而通过widesolve寻找出最优解,这是因为广度搜索先总是找出最短路径。