搜索策略实验.docx
《搜索策略实验.docx》由会员分享,可在线阅读,更多相关《搜索策略实验.docx(16页珍藏版)》请在冰豆网上搜索。
搜索策略实验
实验一:
搜索策略实验
一、实验目的
1、熟悉和掌握启发式搜索的定义、估价函数和算法过程。
2、利用A*算法求解N数码难题,理解求解流程和搜索顺序。
二、实验内容
以八数码为例实现A或A*算法。
1、分析算法中的OPEN表CLOSE表的生成过程。
2、分析估价函数对搜索算法的影响。
3、分析启发式搜索算法的特点。
起始棋局目标棋局
启发式函数选取为:
f*(n)=g*(n)+h*(n)
其中:
g*(n)是搜索树中节点n的深度;
h*(n)用来计算对应于节点n的数据中错放的棋子个数。
三、实验设计与结果
八数码问题是个典型的状态图搜索问题。
搜索方式有两种基本的方式,即树式搜索和线式搜索。
搜索策略大体有盲目搜索和启发式搜索两大类。
盲目搜索就是无“向导”的搜索,启发式搜索就是有“向导”的搜索。
由八数码问题的部分状态图可以看出,从初始节点开始,在通向目标节点的路径上,各节点的数码格局同目标节点相比较,其数码不同的位置个数在逐渐减少,最后为零。
所以,这个数码不同的位置个数便是标志一个节点到目标节点距离远近的一个启发性信息,利用这个信息就可以指导搜索。
即可以利用启发信息来扩展节点的选择,减少搜索范围,提高搜索速度。
由此解决八数码问题就是在初始状态和目标状态两个状态之间寻找一系列可过渡状态。
利用A*算法实现寻找中间状态,从而得到目标状态。
根据启发式搜索算法A*算法的具体步骤,结合八数码问题的要求,从而得出相应的流程图为:
其中:
OPEN表:
算法已搜索但尚未扩展的节点集合。
CLOSED表:
算法已扩展的节点集合。
实验输出结果:
运行程序,输入起始棋局与目标棋局:
结果输出为:
四、程序
1、设定启发式函数:
八数码问题的目标是要搜索到目标节点,所以为了尽快的向目标节点进行靠近,可以把启发式函数设定为当前节点与目标节点中状态的差异,即与目标节点中数码的位置不同的个数作为启发函数的返回值,然后根据启发函数值找出启发值最小的状态节点进行扩展。
2、OPEN表和CLOSE表的生成过程:
OPEN表是用来存放经过扩展得到的待考察的状态节点,CLOSE表是用来存放考察过的状态节点,并且标记上当前节点的编号和父节点的编号,然后可以根据编号便可以形成一个搜索树,即可以找到一个解路径。
其中状态节点的结构体如下所示:
structnode{
inta[3][3];//存放矩阵
intfather;//父节点的位置
intgone;//是否遍历过,1为是,0为否
intfn;//评价函数的值
intx,y;//空格的坐标
intdeep;//节点深度
};
从而可以定义OPEN表和CLOSE表如下:
store.clear();//清空store
vectoropen;//建立open表
open.push_back
(1);//把初始状态在store中的位置数压入open表中
while(!
open.empty()){//当open表不为空时,开始寻找路径
if(check(top))break;
min=top;
inti_min=0;
3、计算启发函数值函数:
intget_fn(intnum)//返回store[num]的评价函数值
{
intfn_temp=0;//评价函数值
booltest=true;
for(inti=0;i<3;i++){//当找到一个值后,计算这个值位置与目标位置的距离差,test置为false后继续寻找下一个值
for(intj=0;j<3;j++){
test=true;
for(intk=0;k<3;k++){
for(intl=0;l<3;l++){
if((store[num].x!
=i||store[num].y!
=j)&&store[num].a[i][j]==store[0].a[k][l]){//寻值时排除空格位
fn_temp=fn_temp+abs(i-k)+abs(j-l);
test=false;
}
if(test==false)break;
}
if(test==false)break;
}
}
}
fn_temp=fn_temp+store[num].deep;//加上节点深度
returnfn_temp;
}
4、判断新扩展节点是否是已经扩展过的的节点函数:
boolsearch(intnum)//判断store[num]节点是否已经扩展过,没有扩展返回true
{
intpre=store[num].father;//pre指向store[num]的父节点位置
booltest=true;
while(!
pre){//循环直到pre为0,既初始节点
for(inti=0;i<3;i++){
for(intj=0;j<3;j++){
if(store[pre].a[i][j]!
=store[num].a[i][j]){
test=false;
break;
}
}
if(test==false)break;
}
if(test==true)returnfalse;
pre=store[pre].father;//pre继续指向store[pre]父节点位置
}
returntrue;
}
5、在open表中找出启发值最小的节点(待扩展)函数:
open.push_back
(1);//把初始状态在store中的位置数压入open表中
while(!
open.empty()){//当open表不为空时,开始寻找路径
if(check(top))break;
min=top;
inti_min=0;
for(i=0;iif(store[open[i]].fn<=store[min].fn&&store[open[i]].gone==0){
min=open[i];
i_min=i;
}
}
store[min].gone=1;
open.erase(open.begin()+i_min);//把最小节点标记遍历过,并从open表中删除
五、实验结论
1、估价函数的值对搜索算法速度的影响
估价函数的任务就是估计待搜索结点的重要程度,给它们排定次序。
评价函数可以是任意一种函数,如定义它是结点x处于最佳路径上的概率,或是x结点和目标结点之间的距离,或是x格局的得分等等。
一般来说,评价一个结点的价值,必须综合考虑两方面的因素:
已付出的代价和将要付出的代价。
在此,我们把评价函数f(n)定义为从初始结点经过n结点到达目标结点的最小代价路径的代价估计值。
A*算法成功与否的关键在于估价函数的正确选择,从理论上说,一个完全正确的估价函数是可以非常迅速地得到问题的正确解答,但一般完全正确的估价函数是得不到的,因而A*算法不能保证它每次都得到正确解答。
一个不理想的估价函数可能会使它工作得很慢,甚至会给出错误的解答。
如果对其估价函数中的h*(n)部分即启发性函数,加以适当的单调性限制条件,就可以使它对所扩展的一系列节点的估价函数值单调递增(或非递减),从而减少对OPEN表或CLOSED表的检查和调整,提高搜索效率。
为了提高解答的正确性,我们还可以适当地降低估价函数的值,从而使之进行更多的搜索,但这是以降低它的速度为代价的,因而我们可以根据实际对解答的速度和正确性的要求而设计出不同的方案,使之更具弹性。
2、启发式搜索的特点
对于那些受大自然的运行规律或者面向具体问题的经验、规则启发出来的方法,人们常常称之为启发式算法(HeuristicAlgorithm)。
现在的启发式算法也不是全部来自自然的规律,也有来自人类积累的工作经验。
启发式算法有不同的定义:
一种定义为,一个基于直观或经验的构造的算法,对优化问题的实例能给出可接受的计算成本(计算时间、占用空间等)内,给出一个近似最优解,该近似解于真实最优解的偏离程度不一定可以事先预计;另一种是,启发式算法是一种技术,这种技术使得在可接受的计算成本内去搜寻最好的解,但不一定能保证所得的可行解和最优解,甚至在多数情况下,无法阐述所得解同最优解的近似程度。
启发式搜索就是利用启发性信息进行制导的搜索。
它有利于快速找到问题的解。
其具有的特点是:
从随机的可行初始解出发,才用迭代改进的策略,去逼近问题的最优解。
附:
源程序
#include
#include
#include
usingnamespacestd;
structnode{
inta[3][3];//存放矩阵
intfather;//父节点的位置
intgone;//是否遍历过,1为是,0为否
intfn;//评价函数的值
intx,y;//空格的坐标
intdeep;//节点深度
};
vectorstore;//存放路径节点
intmx[4]={-1,0,1,0};
intmy[4]={0,-1,0,1};//上下左右移动数组
inttop;//当前节点在store中的位置
boolcheck(intnum)//判断store[num]节点与目标节点是否相同,目标节点储存在store[0]中
{
for(inti=0;i<3;i++){
for(intj=0;j<3;j++){
if(store[num].a[i][j]!
=store[0].a[i][j])
returnfalse;
}
}
returntrue;
}
boolsearch(intnum)//判断store[num]节点是否已经扩展过,没有扩展返回true
{
intpre=store[num].father;//pre指向store[num]的父节点位置
booltest=true;
while(!
pre){//循环直到pre为0,既初始节点
for(inti=0;i<3;i++){
for(intj=0;j<3;j++){
if(store[pre].a[i][j]!
=store[num].a[i][j]){
test=false;
break;
}
}
if(test==false)break;
}
if(test==true)returnfalse;
pre=store[pre].father;//pre继续指向store[pre]父节点位置
}
returntrue;
}
voidprint(intnum)//打印路径,store[num]为目标节点
{
vectortemp;//存放路径
intpre=store[num].father;
temp.push_back(num);
while(pre!
=0){//从目标节点回溯到初始节点
temp.push_back(pre);
pre=store[pre].father;
}
cout<cout<<"路径为:
"<for(intm=temp.size()-1;m>=0;m--){
for(inti=0;i<3;i++){
for(intj=0;j<3;j++){
cout<}
cout<}
cout<}
cout<<"所需步数为:
"<return;
}
intget_fn(intnum)//返回store[num]的评价函数值
{
intfn_temp=0;//评价函数值
booltest=true;
for(inti=0;i<3;i++){//当找到一个值后,计算这个值位置与目标位置的距离差,test置为false后继续寻找下一个值
for(intj=0;j<3;j++){
test=true;
for(intk=0;k<3;k++){
for(intl=0;l<3;l++){
if((store[num].x!
=i||store[num].y!
=j)&&store[num].a[i][j]==store[0].a[k][l]){//寻值时排除空格位
fn_temp=fn_temp+abs(i-k)+abs(j-l);
test=false;
}
if(test==false)break;
}
if(test==false)break;
}
}
}
fn_temp=fn_temp+store[num].deep;//加上节点深度
returnfn_temp;
}
voidkongxy(intnum)//获得空格坐标
{
for(inti=0;i<3;i++){
for(intj=0;j<3;j++){
if(store[num].a[i][j]==0){
store[num].x=i;
store[num].y=j;
}
}
}
return;
}
intmain()
{
while(true){
store.clear();//清空store
vectoropen;//建立open表
inti,j,m,n,f;
intmin;//store[min]储存fn值最小的节点
inttemp;
booltest;
top=1;//当前节点在store的位置,初始节点在store[1]
inttarget[9];
intbegin[9];//储存初始状态和目标状态,用于判断奇偶
intt1=0,t2=0;//初始状态和目标状态的奇偶序数
nodenode_temp;
store.push_back(node_temp);
store.push_back(node_temp);//用于创建store[0]和store[1],以便下面使用
cout<<"请输入初始状态,1~8数字,0代表空格:
"<test=false;
while(test==false){
f=0;
for(i=0;i<3;i++){
for(j=0;j<3;j++){
cin>>temp;
store[1].a[i][j]=temp;
begin[f++]=temp;
}
}
test=true;
for(i=0;i<8;i++){//检查是否有重复输入,若有则重新输入
for(j=i+1;j<9;j++){
if(begin[i]==begin[j]){
test=false;
break;
}
}
if(test==false)break;
}
if(test==false)cout<<"输入重复,请重新输入:
"<}
kongxy
(1);//找出空格的坐标
cout<<"请输入目标状态,1~8数字,0代表空格:
"<test=false;
while(test==false){
f=0;
for(i=0;i<3;i++){
for(j=0;j<3;j++){
cin>>temp;
store[0].a[i][j]=temp;
target[f++]=temp;
}
}
test=true;
for(i=0;i<8;i++){//检查是否有重复输入,若有则重新输入
for(j=i+1;j<9;j++){
if(target[i]==target[j]){
test=false;
break;
}
}
if(test==false)break;
}
if(test==false){
cout<<"输入重复,请重新输入:
"<continue;//若重复,重新输入
}
for(i=0;i<9;i++){//检查目标状态与初始状态是否匹配
test=false;
for(j=0;j<9;j++){
if(begin[i]==target[j]){
test=true;
break;
}
}
if(test==false)break;
}
if(test==false)cout<<"输入与初始状态不匹配,请重新输入:
"<}
for(i=1;i<9;i++){//判断奇偶序数是否相同,若不相同则无法找到路径
for(j=1;i-j>=0;j++){
if(begin[i]>begin[i-j]){
if(begin[i-j]!
=0)t1++;
}
}
}
for(i=1;i<9;i++){
for(j=1;i-j>=0;j++){
if(target[i]>target[i-j]){
if(target[i-j]!
=0)t2++;
}
}
}
if(!
(t1%2==t2%2)){
cout<<"初始状态与目标状态奇偶不同,无法找到路径."<cout<//system("pause");
//return0;
continue;
}
store[1].father=0;//初始化参数
store[1].gone=0;
store[1].deep=0;//初始节点的父节点为0
store[1].fn=get_fn
(1);
if(check
(1)){//判断初始状态与目标状态是否相同
print
(1);
//system("pause");
//return0;
cout<continue;
}
open.push_back
(1);//把初始状态在store中的位置数压入open表中
while(!
open.empty()){//当open表不为空时,开始寻找路径
if(check(top))break;
min=top;
inti_min=0;
for(i=0;iif(store[open[i]].fn<=store[min].fn&&store[open[i]].gone==0){
min=open[i];
i_min=i;
}
}
store[min].gone=1;
open.erase(open.begin()+i_min);//把最小节点标记遍历过,并从open表中删除
m=store[min].x;
n=store[min].y;//空格坐标
for(f=0;f<4;f++){//上下左右移动空格
i=m+mx[f];
j=n+my[f];
if(i>=0&&i<=2&&j>=0&&j<=2){//当变换后的空格坐标在矩阵中时,开始移动
top++;
store.push_back(store[min]);//把store[min]压入store中成为新增节点,位置为store[top]
store[top].father=min;//新增节点的父节点为min
store[top].gone=0;//新增节点未被访问
store[top].deep=store[min].deep+1;//新增节点的深度为父节点深度+1
temp=store[top].a[m][n];//交换空格与相邻数字
store[top].a[m][n]=store[top].a[i][j];
store[top].a[i][j]=temp;
store[top].x=i;//移动后的空格坐标
store[top].y=j;
store[top].fn=get_fn(top);//移动后的fn值
open.push_back(top);//把top压入open表中
if(check(top)){//检查是否到达目标
print(top);
//system("pause");
//return0;
break;
}
if(search(top)==false){//检查新增节点是否被访问过,若访问过,则删除此节点
top--;
store.pop_back();
open.pop_back();
}
}
}
}
cout<}
return0;
system("pause");
}