前言
前几周在hihoCoder上看到的一关于八数码的问题(#1312:搜索三-启发式搜索)。借着提示一步一步编完了脚本,关键要点在于启发式搜索算法及其启发函数的设计。
编译环境:Linux G++
详细代码
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <string.h>
#include <list>
#include <math.h>
#include <algorithm>
using namespace std;
#define COL 3
const char *splitChar = " ";
int dataCount=0;
int data[COL][COL];
typedef struct index
{
int status[COL][COL];
int g;
int h;
int f;
bool operator==(const index &rhs)const
{
for(int i=0;i<COL;i++)
{
for(int j=0;j<COL;j++)
{
if(status[i][j]!=rhs.status[i][j])
return false;
}
}
return true;
}
list<index> neighborStatus()
{
list<index> l;
int col=-1,row=-1;
for(int i=0;i<COL;i++)
{
for(int j=0;j<COL;j++)
{
if(status[i][j]==0)
{
col=i;row=j;
break;
}
}
if(col!=-1)
break;
}
if(row>0)
{
index inx;
for(int i=0;i<COL;i++)
{
for(int j=0;j<COL;j++)
{
inx.status[i][j]=status[i][j];
}
}
inx.status[col][row]=inx.status[col][row-1];
inx.status[col][row-1]=0;
l.push_back(inx);
}
if(row<COL-1)
{
index inx;
for(int i=0;i<COL;i++)
{
for(int j=0;j<COL;j++)
{
inx.status[i][j]=status[i][j];
}
}
inx.status[col][row]=inx.status[col][row+1];
inx.status[col][row+1]=0;
l.push_back(inx);
}
if(col>0)
{
index inx;
for(int i=0;i<COL;i++)
{
for(int j=0;j<COL;j++)
{
inx.status[i][j]=status[i][j];
}
}
inx.status[col][row]=inx.status[col-1][row];
inx.status[col-1][row]=0;
l.push_back(inx);
}
if(col<COL-1)
{
index inx;
for(int i=0;i<COL;i++)
{
for(int j=0;j<COL;j++)
{
inx.status[i][j]=status[i][j];
}
}
inx.status[col][row]=inx.status[col+1][row];
inx.status[col+1][row]=0;
l.push_back(inx);
}
return l;
}
}SearchIndex;
typedef list<SearchIndex> IndexList;
IndexList openList,closeList;
IndexList::iterator iter;
SearchIndex getMinFStatus(IndexList &indexList)
{
iter=indexList.begin();
IndexList::iterator it=iter;
int min=iter->f;
while(iter !=indexList.end())
{
if(iter->f<min)
it=iter;
iter++;
}
return *it;
}
int evaluate(int data[][COL])
{
//求曼哈顿距离
int dis=0;
for(int i=0;i<COL;i++)
{
for(int j=0;j<COL;j++)
{
if(data[i][j]==0)
{
continue;
}
int row=(data[i][j]-1)/3;
int col=data[i][j]-row*3-1;
dis+=abs(i-row)+abs(j-col);
}
}
//dis/=2;
return dis;
}
bool isCanCompute(int data[][COL])
{
//判断是否有解
int sum=0;
int copyData[COL*COL];
for(int i=0;i<COL*COL;i++)
{
int row=i/3;
int col=i-row*3;
copyData[i]=data[row][col];
}
for(int i=0;i<COL*COL-1;i++)
{
if(copyData[i]==0)
continue;
for(int j=i;j<COL*COL;j++)
{
if(copyData[j]==0)
continue;
if(copyData[j]<copyData[i])
{
sum++;
}
}
}
return sum%2==0;
}
int search(int data[][COL])
{
if(!isCanCompute(data))
return -1;
openList.clear();
closeList.clear();
SearchIndex start;
for(int i=0;i<COL;i++)
{
for(int j=0;j<COL;j++)
{
start.status[i][j]=data[i][j];
}
}
start.g=0;
start.h=evaluate(start.status);
start.f=start.g+start.h;
openList.push_back(start);
while(!openList.empty())
{
SearchIndex u=getMinFStatus(openList);
if(u.h==0)
return u.f;
closeList.push_back(u);
IndexList neighborList=u.neighborStatus();
openList.remove(u);
//cout<<"u.g:"<<u.g<<"\tu.h:"<<u.h;
iter=neighborList.begin();
while(iter!=neighborList.end())
{
IndexList::iterator it=openList.begin();
it=find(openList.begin(),openList.end(),*iter);
if(it!=openList.end())
{
//更新v的f值
if(iter->f>iter->h+u.g+1)
iter->f=iter->h+u.g+1;
}
else{
it=closeList.begin();
it=find(closeList.begin(),closeList.end(),*iter);
if(it!=closeList.end())
{
}
else
{
iter->g=u.g+1;
iter->h=evaluate(iter->status);
iter->f=iter->g+iter->h;
openList.push_back(*iter);
}
}
iter++;
}
}
return -1;
}
int main()
{
cin>>dataCount;
int *res=new int[dataCount];
char *s=new char[COL*2];
for(int j=1;j<=dataCount;j++)
{
for(int i=1;i<=COL;i++)
{
for(int row=0;row<COL;row++)
{
cin>>data[i-1][row];
}
}
res[j-1]=search(data);
}
for(int i=0;i<dataCount;i++)
{
if(res[i]==-1)
{
cout<<"No Solution!"<<endl;
continue;
}
cout<<res[i]<<endl;
}
return 0;
}
要点
①启发式搜索:一般的宽度优先搜索即对每一步计算最优步,对于之前已经计算过的步用较优解替代,最短路径算法就是最好的例子。启发式搜索简单的来说就是对于计算开销,在宽度优先搜索算法计算的开销基础上加上启发函数计算的开销,即F=G+H。
G:就是普通宽度优先搜索中的从起始状态到当前状态的代价,比如在这次的问题中,G就等于从起始状态到当前状态的最少步数。
H:是由启发函数计算的一个估计的值,表示从当前状态到目标状态估计的代价(步数)。
②启发函数设计:H是由我们自己设计的,其设计的好坏决定了搜索的效率。H越大,算法运行越快。但需要注意的一个点,启发函数的值一定要小于实际从当前态到目标态的最小步骤。否则即使运行效率加快,也可能会遗漏了最优解。可以这么理解,对于F=G+H,由于我们每次搜索方向是朝着最小F走的,随着H的增大,预估的步数占比实际的步数越大,H起着引导下一步往期望最优解走的作用,当H超过实际步数,就失去了指示到最优解的可能,即存在朝着其他更小F但不是最优解的方向走的可能。
③查询最小元素:这是提升搜索性能以一个小要点。由于我们每次查询最小元素都在openList中,它是一个动态变化的数组/链表,我们可以使用最小堆优化查询效率。