以前写拼图游戏的时候就有个疑问:如果随机生成每个图片的位置的话,这个拼图可能是永远也解不出来的。但是当时不知道如何去解一个九宫格问题。
最近了解了一下搜索算法,发现其实很多很多的问题都可以归结为对状态空间树的搜索,搜出来最优解。但是问题的解空间往往是巨大的,超出了计算机的计算能力。搜的所称中如何减少向下搜索的分支非常重要。而启发式搜索算法,着重于先搜那些可能快速达到终点状态的分支,来减少搜索时间。
启发式算法的重点在于如何启发,这是个经验加艺术的技术。启发式算法的启发函数就像是hash表的hash函数一样,设计好了非常牛;空间效率,时间效率都别的算法无可比拟的,甚至说是计算机不可超越的。
练练手,写了一个九宫格的程序。
代码如下(启发函数太简单,复杂情况计算很慢):
//用A*算法解决9宫格问题
//
#include<assert.h>
#include<iostream>
#include<algorithm>
#include<time.h>
#include<queue>
#include<string>
#include<vector>
using namespace std;
int rem[10][2]={{0,0},{0,0},{0,1},{0,2},{1,2},{2,2},{2,1},{2,0},{1,0},{1,1}};
struct Node
{
char data[3][3]; //记录各个格子里的数字
string key; //当前节点的键值 用以唯一标示一个结点 为简便起见,我们用 data[0][0]+data[[0][1]
//+data[0][2]+data[1][0]+...data[2][2]表示
string theWay; //记录父节点如何移动后产生了当前节点 用以输出结果时 向上查找路径
//'1'向左 '2'向右 '3' 向上 '4'向下
int dist; //当前结点里目标结点的“距离” “距离”越小,表示当前结点能通过少量的移动即可达到目标节点
int dept; //当前节点在搜索树种深度
bool moveLeft(string &next)
{
int i,j;
for(i=0;i<3;i++)
{
for(j=0;j<3;j++)
{
if(data[i][j]=='9')
break;
}
if(j!=3)
break;
}
if(j>0) //可以左移
{
next=key;
swap(next[3*i+j],next[3*i+j-1]);
return true;
}
return false;
}
bool moveRight(string &next)
{
int i,j;
for(i=0;i<3;i++)
{
for(j=0;j<3;j++)
{
if(data[i][j]=='9')
break;
}
if(j!=3)
break;
}
if(j<2) //可以右移
{
next=key;
swap(next[3*i+j],next[3*i+j+1]);
return true;
}
return false;
}
bool moveDown(string &next)
{
int i,j;
for(i=0;i<3;i++)
{
for(j=0;j<3;j++)
{
if(data[i][j]=='9')
break;
}
if(j!=3)
break;
}
if(i<2) //可以下移
{
next=key;
swap(next[3*i+j],next[3*(i+1)+j]);
return true;
}
return false;
}
bool moveUp(string &next)
{
int i,j;
for(i=0;i<3;i++)
{
for(j=0;j<3;j++)
{
if(data[i][j]=='9')
break;
}
if(j!=3)
break;
}
if(i>0) //可以上移
{
next=key;
swap(next[3*i+j],next[3*(i-1)+j]);
return true;
}
return false;
}
bool isFinal()
{
if(key=="123894765")
return true;
return false;
}
friend bool operator < (const Node &node1,const Node &node2)
{
return node1.dist>node2.dist;
}
Node(string Key,int depth,string par)
{
theWay=par;
dept=depth;
key=Key;
for(int i=0;i<9;i++)
{
data[i/3][i%3]=key[i];
}
//计算dist
dist=dept;
/*for(int i=1;i<9;i++)
{
if(data[rem[i][0]][rem[i][1]]!='9')
{
if(data[rem[i][0]][rem[i][1]]+1 != data[rem[i+1][0]][rem[i+1][1]])
dist+=6;
}
}*/
for(int i=0;i<3;i++)
{
for(int j=0;j<3;j++)
{
dist+= abs(i-rem[data[i][j]-'0'][0])+abs(j-rem[data[i][j]-'0'][1]) ;
}
}
}
};
class NineBox
{
private:
char *first_key;
void input();
void output(Node &final);
public:
NineBox()
{
first_key=new char[10];
}
~NineBox()
{
delete []first_key;
}
void run();
};
void NineBox::output(Node &final)
{
int len=final.theWay.length();
for(int i=0;i<len;i++)
{
switch(final.theWay[i])
{
case '1':
cout<<"向左->";
break;
case '2':
cout<<"向右->";
break;
case '3':
cout<<"向上->" ;
break;
case '4':
cout<<"向下->";
break;
}
}
cout<<"成功!"<<endl;
}
void NineBox::input( )
{
cout<<"请输入九宫格的初始状态字符串:"<<endl;
while(cin>>first_key)
{
char *temp=new char[10];
memcpy(temp,first_key,10);
sort(temp,temp+9);
int i;
for(i=0;i<9;i++)
{
if(temp[i]!=('1'+i))
break;
}
if(i==9)
break;
else
{
cout<<"输入不合法,请重新输入:"<<endl;
}
}
}
void NineBox::run()
{
priority_queue<Node> open; //未访问过的结点表
vector<string> closed; //访问过的结点表
input();
Node start(first_key,0,"");
open.push(start);
//放入closed表
closed.push_back(start.key);
while(!open.empty())
{
Node now=open.top();
open.pop();
if(now.isFinal()) //找到路径
{
output(now);
return ;
}
string next="";
if(now.moveLeft(next))
{
if(!( find(closed.begin(),closed.end(),next)!=closed.end()))
{
Node temp(next,now.dept+1,now.theWay+'1');
open.push(temp);
closed.push_back(next);
}
}
if(now.moveRight(next))
{
if(!( find(closed.begin(),closed.end(),next)!=closed.end()))
{
Node temp(next,now.dept+1,now.theWay+'2');
open.push(temp);
closed.push_back(next);
}
}
if(now.moveUp(next))
{
if(!( find(closed.begin(),closed.end(),next)!=closed.end()))
{
Node temp(next,now.dept+1,now.theWay+'3');
open.push(temp);
closed.push_back(next);
}
}
if(now.moveDown(next))
{
if(!( find(closed.begin(),closed.end(),next)!=closed.end()))
{
Node temp(next,now.dept+1,now.theWay+'4');
open.push(temp);
closed.push_back(next);
}
}
}
}
int main()
{
NineBox *myNineBox=new NineBox();
myNineBox->run();
return 0;
}