- 问题描述
八数码问题作为一个经典的问题被大家所熟知,该问题是求解如何从开始的一个状态(布局)到达目标状态所需步数最少的问题。百度百科详情 问题分析
将每一个状态作为一个结点容易想到可以用广搜的方法解决,这种方法简单,但是就算是加入哈希判重也会搜索很多的无用结点。
我们打算用A*算法解决这个问题,既然确定了用A*算法,那么我们首先应该确定估价函数h(x),估价函数的选取直接决定A*算法的效率,一般对于八数码问题有三种估价函数的选法:- 以不在位的数码的个数为估价函数
- 以不在位的数码归位所需的最短距离和即曼哈顿距离为估价函数
- 将逆序对数作为估价函数
可以证明前两种都是乐观估计,最后一种不是,因此前两种都可以作为八数码问题的估价函数,但是你的估计值与真实值越近所需要搜索的状态越少,很明显第一种方法太乐观了(估价函数的选取直接决定算法的效率),因此我们采用第二种方法作为八数码问题的估价函数
解决了估价函数的问题以后,第二个需要解决的问题就是判重,我们首先想到的是用集合set,这种方法最简单,但是很不幸这种方法耗时也是最多的,如果时间要求比较高的话,这种情况很容易超时。这里我们不用这种方法,判重问题自然而然想到的是哈希表,好了现在问题又来了,如何创建哈希表,也就是哈希函数怎么写,这个东西比较有技巧,还好对于这种问题有一种现成的方法解决,那就是康托展开 ,还有一个问题就是有些问题是无解的,这种情况我们不希望进行很大力气的搜索之后发现无解,最好是能提前预知,值得庆幸的是八数码无论怎么移动逆序的奇偶性不变,因此我们可以直接通过O(1)的时间判断开始和目标结点的逆序奇偶性是否相同就可以了。有了上面的分析之后,程序就可以写出来了
自己写代码的话,可以用一个一维数组存储状态,二维数组实在有些麻烦代码如下
C++代码
/** A*算法解决八数码问题(九宫重排) * 程序看起来比较长,核心只有 int Astar(int [][COL],int [][COL],int,int);函数 * 其它函数供Astar函数调用,起辅助作用,还有几个函数仅仅是为了使界面更友好 * 所有函数均有注释说明 * 其中可行性判断函数需要对八数码问题进行数学上的简单分析, * hash函数的设计有些技巧,其他函数的原理都是显然的 * 程序运行有问题可以和我联系 */
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<queue>
#define COL 3
#define MAXSTEP 70
using namespace std;
void output(int[][COL]);/*输出函数*/
void input(int [][COL]);/*输出函数*/
int Astar(int [][COL],int [][COL],int,int path[]);/*核心函数,起始,终止,深度,方向*/
bool eq(int from[][COL],int to[][COL]);/*判断起始与终止是否相同*/
bool change(int from[][COL],const int i,const int j,const int step);/*判断当前状态是否可以进行相应移动,并进行状态转变*/
int value(const int from[][COL],const int to[][COL]);/*估价函数*/
void output_tow(int from[][COL],int to[][COL]);/*输出函数,和上面的outpput函数差不多*/
bool possible(int from[][COL],int to[][COL]);/*可行性判断*/
int h[9]={40320,5040,720,120,24,6,2,1,1};/*hash函数用到的数据 8-0的阶乘*/
bool ha[400000];
struct Node{
int path[MAXSTEP]; /*路径信息*/
int expend;/*权重*/
int deep;/*深度*/
int x[COL][COL];/*状态信息*/
};
struct cmp{
bool operator() (const Node A,const Node B){
return A.expend>B.expend;
}
};
int pa[MAXSTEP];
priority_queue<Node,vector<Node>,cmp> q;/*优先队列*/
Node make(int from[][COL],int deep,int v,int path[],int step);/*转换函数*/
int main()
{
int from[COL][COL];
int to[COL][COL];
int k=0,c;
memset(ha,0,sizeof(ha));
memset(pa,-1,sizeof(pa));
printf("请按行输入原始九宫格,空白的输入0\n");
input(from);
printf("原始九宫格为:\n");
output(from);
printf("请按行输入目标九宫格,空白的输入0\n");
input(to);
printf("目标九宫格为:\n");
output(to);
printf("按任意键显示执行步骤:\n");
fflush(stdin);
getchar();
if(!possible(from,to)){
cout<<"目标状态不可达,请换一组数据测试!"<<endl;
return 0;
}
int d=Astar(from,to,0,pa);
cout<<"最优路径到目标位置需要"<<d<<"步"<<endl;
cout<<"当前状态"<<'\t'<<"目标状态"<<endl;
while((c=pa[++k])!=-1){
int i,j;/*记录当前状态,白板位置*/
cout<<"第"<<k<<"步"<<endl;
for(i=0;i<3;i++)
for(j=0;j<3;j++)
if(from[i][j]==0)
goto o;
o:
change(from,i,j,pa[k]);
output_tow(from,to);
cout<<"--------------------------------------"<<endl;
}
return 0;
}
void output(int a[][COL])
{
int i,j;
for(i=0;i<COL;i++){
for(j=0;j<COL;j++)
printf("%d ",a[i][j]);
putchar('\n');
}
}
void output_tow(int from[][COL],int to[][COL])
{
int i,j;
for(i=0;i<COL;i++){
for(j=0;j<COL;j++)
printf("%d ",from[i][j]);
cout<<'\t'<<'\t';
for(j=0;j<COL;j++)
printf("%d ",to[i][j]);
putchar('\n');
}
}
void input(int a[][COL])
{
int i,j,c;
s:
int g[9];
memset(g,0,sizeof(g));
for(i=0;i<COL;i++)
for(j=0;j<COL;j++){
scanf("%d",&a[i][j]);
c=a[i][j];
if(g[c]||c<0||c>8){
cout<<"输入有误,请重新输入"<<endl;
goto s;
}
g[c]++;
}
}
int Astar(int from[][COL],int to[][COL],int deep,int path[])
{
if(eq(from,to)){
memcpy(pa,path,sizeof(pa));
return deep;
}
int i,j;/*记录当前状态,白板位置*/
int *a=from[0];
int b[9];
int m=0;
for(i=0;i<9;i++)
b[i]=a[i];
for(i=0;i<9;i++){
for(j=0;j<i;j++)
if(b[i]>a[j])
b[i]--;
m+=h[i]*b[i];
}
ha[m]=1;
for(i=0;i<3;i++)
for(j=0;j<3;j++)
if(from[i][j]==0)
goto ok;
ok: for(int step=0;step<4;step++){
if(change(from,i,j,step)){
int v=value(from,to)+deep+1;
Node n;
n=make(from,deep+1,v,path,step);
q.push(n);
change(from,i,j,step);
}
}
Node p=q.top();
int flag=0;
while(!flag){
a=p.x[0];
m=0;
for(i=0;i<9;i++)
b[i]=a[i];
for(i=0;i<9;i++){
for(j=0;j<i;j++)
if(b[i]>a[j])
b[i]--;
m+=h[i]*b[i];
}
if(!ha[m]){
q.pop();
break;
}
q.pop();
p=q.top();
}
return Astar(p.x,to,p.deep,p.path);
}
bool eq(int from[][COL],int to[][COL])/*判断起始与终止是否相同*/
{
for(int i=0;i<3;i++)
for(int j=0;j<3;j++){
if(from[i][j]!=to[i][j])
return 0;
}
return 1;
}
bool change(int from[][COL],const int i,const int j,const int step)/*判断当前状态是否可以进行相应移动*/
{
if((i==0&&step==0)||(i==2&&step==1)||(j==0&&step==2)||(j==2&&step==3))
return 0;
int a=from[i][j];
switch(step){
case 0:
from[i][j]=from[i-1][j];
from[i-1][j]=a;
break;
case 1:
from[i][j]=from[i+1][j];
from[i+1][j]=a;
break;
case 2:
from[i][j]=from[i][j-1];
from[i][j-1]=a;
break;
case 3:
from[i][j]=from[i][j+1];
from[i][j+1]=a;
break;
default:
cout<<"WRONG!"<<endl;
break;
}
return 1;
}
int value(const int from[][COL],const int to[][COL])/*估价函数*/
{
int i,j,m,n;
int v=0;
for(i=0;i<3;i++)
for(j=0;j<3;j++){
for(m=0;m<3;m++)
for(n=0;n<3;n++)
if(from[i][j]==to[m][n])
goto p;
p:
if(from[i][j]!=0)
v+=(abs(i-m)+abs(j-n));
}
return v;
}
Node make(int from[][COL],int deep,int v,int path[],int step)/*转换函数*/
{
Node p;
for(int i=0;i<3;i++)
for(int j=0;j<3;j++){
p.x[i][j]=from[i][j];
}
p.deep=deep;
p.expend=v;
memcpy(p.path,path,sizeof(int)*MAXSTEP);
p.path[deep]=step;
return p;
}
bool possible(int from[][COL],int to[][COL])/*可行性判断*/
{
int m=0,n=0;
int i,j,k,l;
int a[COL*COL],b[COL*COL];
for(i=0;i<COL;i++)
for(j=0;j<COL;j++){
a[i*COL+j]=from[i][j];
b[i*COL+j]=to[i][j];
}
for(k=0;k<COL*COL;k++)
for(l=k+1;l<COL*COL;l++){
if(a[l]<a[k]&&a[l]!=0)
m++;
if(b[l]<b[k]&&b[l]!=0)
n++;
}
return (n%2)==(m%2);
}
欢迎转载,转载注明出处