人工智能实验题目.docx
《人工智能实验题目.docx》由会员分享,可在线阅读,更多相关《人工智能实验题目.docx(13页珍藏版)》请在冰豆网上搜索。
人工智能实验题目
《人工智能》实验题目
实验一 图的宽度优先搜索算法实验
一、实验目的:
熟悉和掌握盲目式搜索的定义和图的搜索算法过程,并利用图的宽度优先搜索算法求解N数码难题,理解求解流程和搜索顺序。
二、实验原理:
图的宽度优先搜索是一种盲目搜索算法,其特点在于每次把扩展节点过程中产生的新节点插入Open表中的尾部,扩展节点时总是选择Open表中的头节点作为扩展节点。
三、实验条件:
1 N数码难题演示程序。
(
2VC6.0或Java。
三、实验内容:
1 分别以8数码和15数码为例实际求解图的宽度优先搜索算法。
2 画出图的宽度优先搜索算法求解框图。
3 分析图的宽度优先搜索算法的特点。
四、实验步骤:
1 开始演示。
进入N数码难题演示程序,可选8数码或者15数码,点击“选择数码”按钮确定。
第一次启动后,点击两次“缺省”或者“随机”按钮,才会出现图片。
2 点击“缺省棋局”,会产生一个固定的初始节点。
点击“随机生成”,会产生任意排列的初始节点。
3 算法执行。
点击“连续执行”则程序自动搜索求解,并演示每一步结果;点击“单步运行”则每次执行一步求解流程。
“运行速度”可自由调节。
4 观察运行过程和搜索顺序,理解启发式搜索的原理。
在下拉框中选择演示“15数码难题”,点击“选择数码”确定选择;运行15数码难题演示实例。
5 算法流程的任一时刻的相关状态,以算法流程高亮、open表、close表、节点静态图、当前扩展节点移动图等5种形式在按钮上方同步显示,便于深入学习理解图的宽度优先搜索算法。
6 根据程序运行过程画出图的宽度优先搜索算法框图。
五、实验报告要求:
1 图的宽度优先搜索算法流程图和算法框图。
2 根据图的宽度优先搜索算法分析盲目式搜索的特点。
实验二 A*算法实验
一、实验目的:
熟悉和掌握启发式搜索的定义、估价函数和算法过程,并利用A*算法求解N数码难题,理解求解流程和搜索顺序。
二、实验原理:
A*算法是一种有序搜索算法,其特点在于对估价函数的定义上。
对于一般的有序搜索,总是选择f值最小的节点作为扩展节点。
因此,f是根据需要找到一条最小代价路径的观点来估算节点的,所以,可考虑每个节点n的估价函数值为两个分量:
从起始节点到节点n的代价以及从节点n到达目标节点的代价。
三、实验条件:
1 N数码难题演示程序。
3VC6.0或Java。
三、实验内容:
1 分别以8数码和15数码为例实际求解A*算法。
2 画出A*算法求解框图。
3 分析估价函数对搜索算法的影响。
4 分析A*算法的特点。
四、实验步骤:
1 开始演示。
进入N数码难题演示程序,可选8数码或者15数码,点击“选择数码”按钮确定。
第一次启动后,点击两次“缺省”或者“随机”按钮,才会出现图片。
2 点击“缺省棋局”,会产生一个固定的初始节点。
点击“随机生成”,会产生任意排列的初始节点。
3 算法执行。
点击“连续执行”则程序自动搜索求解,并演示每一步结果;点击“单步运行”则每次执行一步求解流程。
“运行速度”可自由调节。
4 观察运行过程和搜索顺序,理解启发式搜索的原理。
在下拉框中选择演示“15数码难题”,点击“选择数码”确定选择;运行15数码难题演示实例。
5 算法流程的任一时刻的相关状态,以算法流程高亮、open表、close表、节点静态图、当前扩展节点移动图等5种形式在按钮上方同步显示,便于深入学习理解A*算法。
6 根据程序运行过程画出A*算法框图。
五、实验报告要求:
1 A*算法流程图和算法框图。
2 试分析估价函数的值对搜索算法速度的影响。
3 根据A*算法分析启发式搜索的特点。
一、题目说明:
(九宫问题)在一个3×3的九宫中有1-8这8个数及一个空格随机的摆放在其中的格子里,如图1-1所示。
现在要求实现这个问题:
将该九宫格调整为如图1-1右图所示的形式。
调整的规则是:
每次只能将与空格(上、下、或左、右)相邻的一个数字平移到空格中。
试编程实现这一问题的求解。
(图1-1)
二、题目分析:
九宫问题是人工智能中的经典难题之一,问题是在3×3方格棋盘中,放8格数,剩下的没有放到的为空,每次移动只能是和相邻的空格交换数。
程序自动产生问题的初始状态,通过一系列交换动作将其转换成目标排列(如下图1-2到图1-3的转换)。
(图1-2) (图1-3)
九宫问题中,程序产生的随机排列转换成目标共有两种可能,而且这两种不可能同时成立,也就是奇数排列和偶数排列。
我们可以把一个随机排列的数组从左到右从上到下用一个一维数组表示,如上图1-2我们就可以表示成{8,7,1,5,2,6,3,4,0}其中0代表空格。
在这个数组中我们首先计算它能够重排列出来的结果,公式就是:
∑(F(X))=Y,其中F(X)
就是一个数他前面比这个数小的数的个数,Y为奇数和偶数个有一种解法。
那么上面的数组我们就可以解出它的结果。
F(8)=0;
F(7)=0;
F(1)=0;
F(5)=1;
F(2)=1;
F(6)=3;
F(3)=2;
F(4)=3;
Y=0+0+0+1+1+3+2+3=10
Y=10是偶数,所以他的重排列就是如图1-3的结果,如果加起来的结果是奇数重排的结果就是如图1-1最右边的排法。
三、算法分析
九宫问题的求解方法就是交换空格(0)位置,直至到达目标位置为止。
图形表示就是:
(图3-1)
要想得到最优的就需要使用广度优先搜索,九宫的所以排列有9!
种,也就是362880种排法,数据量是非常大的,我使用的广度搜索,需要记住每一个结点的排列形式,要是用数组记录的话会占用很多的内存,我们把数据进行适当的压缩。
使用DWORD形式保存,压缩形式是每个数字用3位表示,这样就是3×9=27个字节,由于8的二进制表示形式1000,不能用3位表示,我使用了一个小技巧就是将8表示位000,然后用多出来的5个字表示8所在的位置,就可以用DWORD表示了。
用移位和或操作将数据逐个移入,比乘法速度要快点。
定义了几个结果来存储遍历到了结果和搜索完成后保存最优路径。
类结构如下:
classCNineGird
{
public:
structPlaceList
{
DWORDPlace;
PlaceList*Left;
PlaceList*Right;
};
structScanbuf
{
DWORDPlace;
intScanID;
};
structPathList
{
unsignedcharPath[9];
};
private:
PlaceList*m_pPlaceList;
Scanbuf*m_pScanbuf;
RECTm_rResetButton;
RECTm_rAutoButton;
public:
intm_iPathsize;
clock_tm_iTime;
UINTm_iStepCount;
unsignedcharm_iTargetChess[9];
unsignedcharm_iChess[9];
HWNDm_hClientWin;
PathList*m_pPathList;
boolm_bAutoRun;
private:
inlineboolAddTree(DWORDplace,PlaceList*&parent);
voidFreeTree(PlaceList*&parent);
inlinevoidArrayToDword(unsignedchar*array,DWORD&data);
inlinevoidDwordToArray(DWORDdata,unsignedchar*array);
inlineboolMoveChess(unsignedchar*array,intway);
boolEstimateUncoil(unsignedchar*array);
voidGetPath(UINTdepth);
public:
voidMoveChess(intway);
boolComputeFeel();
voidActiveShaw(HWNDhView);
voidDrawGird(HDChDC,RECTclientrect);
voidDrawChess(HDChDC,RECTclientrect);
voidReset();
voidOnButton(POINTpnt,HWNDhView);
public:
CNineGird();
~CNineGird();
};
计算随机随机数组使用了vector模板用random_shuffle(,)函数来打乱数组数据,并计算目标结果是什么。
代码:
voidCNineGird:
:
Reset()
{
if(m_bAutoRun)return;
vectorvs;
inti;
for(i=1;i<9;i++)
vs.push_back(i);
vs.push_back(0);
random_shuffle(vs.begin(),vs.end());
random_shuffle(vs.begin(),vs.end());
for(i=0;i<9;i++)
{
m_iChess[i]=vs[i];
}
if(!
EstimateUncoil(m_iChess))
{
unsignedchararray[9]={1,2,3,8,0,4,7,6,5};
memcpy(m_iTargetChess,array,9);
}
else
{
unsignedchararray[9]={1,2,3,4,5,6,7,8,0};
memcpy(m_iTargetChess,array,9);
}
m_iStepCount=0;
}
数据压缩函数实现:
inlinevoidCNineGird:
:
ArrayToDword(unsignedchar*array,DWORD&data)
{
unsignedcharnight=0;
for(inti=0;i<9;i++)
{
if(array[i]==8)
{
night=(unsignedchar)i;
break;
}
}
array[night]=0;
data=0;
data=(DWORD)((DWORD)array[0]<<29|(DWORD)array[1]<<26|
(DWORD)array[2]<<23|(DWORD)array[3]<<20|
(DWORD)array[4]<<17|(DWORD)array[5]<<14|
(DWORD)array[6]<<11|(DWORD)array[7]<< 8|
(DWORD)array[8]<< 5|night);
array[night]=8;
}
解压缩时跟压缩真好相反,解压代码:
inlinevoidCNineGird:
:
DwordToArray(DWORDdata,unsignedchar*array)
{
unsignedcharchtem;
for(inti=0;i<9;i++)
{
chtem=(unsignedchar)(data>>(32-(i+1)*3)&0x00000007);
array[i]=chtem;
}
chtem=(unsignedchar)(data&0x0000001F);
array[chtem]=8;
}
由于可扩展的数据量非常的大,加上我在保存的时候使用的是DWORD类型,将每一步数据都记录在一个排序二叉树中,按从小到大从左到有的排列,搜索的时候跟每次搜索将近万次的形式比较快几乎是N次方倍,把几个在循环中用到的函数声明为内联函数,并在插入的时候同时搜索插入的数据会不会在树中有重复来加快总体速度。
二叉树插入代码:
inlineboolCNineGird:
:
AddTree(DWORDplace,PlaceList*&parent)
{
if(parent==NULL)
{
parent=newPlaceList();
parent->Left=parent->Right=NULL;
parent->Place=place;
returntrue;
}
if(parent->Place==place)
returnfalse;
if(parent->Place>place)
{
returnAddTree(place,parent->Right);
}
returnAddTree(place,parent->Left);
}
计算结果是奇数排列还是偶数排列的代码:
boolCNineGird:
:
EstimateUncoil(unsignedchar*array)
{
intsun=0;
for(inti=0;i<8;i++)
{
for(intj=0;j<9;j++)
{
if(array[j]!
=0)
{
if(array[j]==i+1)
break;
if(array[j]
sun++;
}
}
}
if(sun%2==0)
returntrue;
else
returnfalse;
}
移动到空格位的代码比较简单,只要计算是否会移动到框外面就可以了,并在移动的时候顺便计算一下是不是已经是目标结果,这是用来给用户手工移动是给与提示用的,代码:
inlineboolCNineGird:
:
MoveChess(unsignedchar*array,intway)
{
intzero,chang;
boolmoveok=false;
for(zero=0;zero<9;zero++)
{
if(array[zero]==0)
break;
}
POINTpnt;
pnt.x=zero%3;
pnt.y=int(zero/3);
switch(way)
{
case0:
//up
if(pnt.y+1<3)
{
chang=(pnt.y+1)*3+pnt.x;
array[zero]=array[chang];
array[chang]=0;
moveok=true;
}
break;
case1:
//down
if(pnt.y-1>-1)
{
chang=(pnt.y-1)*3+pnt.x;
array[zero]=array[chang];
array[chang]=0;
moveok=true;
}
break;
case2:
//left
if(pnt.x+1<3)
{
chang=pnt.y*3+pnt.x+1;
array[zero]=array[chang];
array[chang]=0;
moveok=true;
}
break;
case3:
//right
if(pnt.x-1>-1)
{
chang=pnt.y*3+pnt.x-1;
array[zero]=array[chang];
array[chang]=0;
moveok=true;
}
break;
}
if(moveok&&!
m_bAutoRun)
{
m_iStepCount++;
DWORDtemp1,temp2;
ArrayToDword(array,temp1);
ArrayToDword(m_iTargetChess,temp2);
if(temp1==temp2)
{
MessageBox(NULL,"你真聪明这么快就搞定了!
","^_^",0);
}
}
returnmoveok;
}
我在进行广度搜索时候,将父结点所在的数组索引记录在子结点中了,所以得到目标排列的时候,我们只要从子结点逆向搜索就可以得到最优搜索路径了。
用变量m_iPathsize来记录总步数,具体函数代码:
voidCNineGird:
:
GetPath(UINTdepth)
{
intnow=0,maxpos=100;
UINTparentid;
if(m_pPathList!
=NULL)
{
delete[]m_pPathList;
}
m_pPathList=newPathList[maxpos];
parentid=m_pScanbuf[depth].ScanID;
DwordToArray(m_pScanbuf[depth].Place,m_pPathList[++now].Path);
while(parentid!
=-1)
{
if(now==maxpos)
{
maxpos+=10;
PathList*temlist=newPathList[maxpos];
memcpy(temlist,m_pPathList,sizeof(PathList)*(maxpos-10));
delete[]m_pPathList;
m_pPathList=temlist;
}
DwordToArray(m_pScanbuf[parentid].Place,m_pPathList[++now].Path);
parentid=m_pScanbuf[parentid].ScanID;
}
m_iPathsize=now;
}
动态排列的演示函数最简单了,为了让主窗体有及时刷新的机会,启动了一个线程在需要主窗体刷新的时候,用Slee(UINT)函数来暂停一下线程就可以了。
代码:
unsigned__stdcallMoveChessThread(LPVOIDpParam)
{
CNineGird*pGird=(CNineGird*)pParam;
RECTrect;
pGird->m_iStepCount=0;
:
:
GetClientRect(pGird->m_hClientWin,&rect);
for(inti=pGird->m_iPathsize;i>0;i--)
{
memcpy(pGird->m_iChess,pGird->m_pPathList[i].Path,9);
pGird->m_iStepCount++;
InvalidateRect(pGird->m_hClientWin,&rect,false);
Sleep(300);
}
charmsg[100];
sprintf(msg,"^_^!
搞定了!
\r\n计算步骤用时%d毫秒",pGird->m_iTime);
MessageBox(NULL,msg,"~_~",0);
pGird->m_bAutoRun=false;
return0L;
}
最后介绍一下搜索函数的原理,首先得到源数组,将其转换成DWORD型,与目标比较,如果相同完成,不同就交换一下数据和空格位置,加入二叉树,搜索下一个结果,直到没有步可走了,在搜索刚刚搜索到的位置的子位置,这样直到找到目标结果为止,函数:
boolCNineGird:
:
ComputeFeel()
{
unsignedchar*array=m_iChess;
UINTi;
constintMAXSIZE=362880;
unsignedchartemparray[9];
DWORDtarget,fountain,parent,parentID=0,child=1;
ArrayToDword(m_iTargetChess,target);
ArrayToDword(array,fountain);
if(fountain==target)
{
returnfalse;
}
if(m_pScanbuf!
=NULL)
{
delete[]m_pScanbuf;
}
m_pScanbuf=newScanbuf[MAXSIZE];
AddTree(fountain,m_pPlaceList);
m_pScanbuf[0].Place=fountain;
m_pScanbuf[0].ScanID=-1;
clock_ttim=clock();
while(parentID{
parent=m_pScanbuf[parentID].Place;
for(i=0;i<4;i++) //0:
UP,1:
Down,2:
Left,3:
Right
{
DwordToArray(parent,temparray);
if(MoveChess(temparray,i))//是否移动成功
{
ArrayToDword(temparray,fountain);
if(AddTree(fountain,m_pPlaceList))//加入搜索数
{
m_pScanbuf[child].Place=fountain;
m_pScanbuf[child].ScanID=parentID;
if(fountain==target)//是否找到结果
{
m_iTime=clock()-tim;
GetPath(child);//计算路径
FreeTree(m_pPlaceLis