八数码问题a*算法c语言,八数码问题-启发式搜索(A*算法)

八数码问题也称为九宫问题。在3×3的棋盘上摆有八个棋子,每个棋子上标有1至8的某一数字,不同棋子上标的数字不相同。棋盘上还有一个空格,与空格相邻的棋子可以移到空格中。给出一个初始状态和一个目标状态,求出从初始状态转变成目标状态的移动棋子步数的最少值。

0818b9ca8b590ca3270a3433284dd417.png

一般的目标状态是指下面这样的排列方式。

0818b9ca8b590ca3270a3433284dd417.png

我们先讲解下面这几个子问题,从而一步一步解决它。

1.康托展开和逆康托展开

假设有A,B,C,D四个字母的一个排列D,B,A,C,现在要求这个排列是字典序中的第几个。康托展开的公式是X=an*(n-1)!+an-1*(n-2)!+...+ai*(i-1)!+...+a2*1!+a1*0!, 其中,ai为当前未出现的元素中是排在第几个(从0开始)。在这个例子中,X=a4*3!+a3*2!+a2*1!+a1*0!。

a4=D这个元素在子数组D,B,A,C中是第几大的元素。A是第0大的元素,B是第1大的元素,C是第2大的元素,D是第3大的元素,所以a4=3。

a3=B这个元素在子数组B,A,C中是第几大的元素。A是第0大的元素,B是第1大的元素,C是第2大的元素,所以a3=1。

a2=A这个元素在子数组A,C中是第几大的元素。A是第0大的元素,C是第1大的元素,所以a2=0。

a1=C这个元素在子数组C中是第几大的元素。因为子数组只有1个元素,所以a1=0。

所以,X=3*3!+1*2!+0*1!+0*0!=20。

如果已知某个排列是字典序中的第20个,求出这个排列的过程就是逆康托展开。

0818b9ca8b590ca3270a3433284dd417.png

通过辗转相除法可以知道a4、a3、a2、a1的值,第一个元素是A,B,C,D中第3大的元素D,第二个元素是A,B,C中第1大的元素B,第三个元素是A,C中第0大的元素A,第四个元素是子数组C中第0大的元素C,所以排列为D,B,A,C。

2.怎样判断八数码问题是否有解

在分析之前,先引进逆序和逆序数的概念:对于棋子数列中任何一个棋子c[i](1≤i≤8),如果有j>i且c[j]c[i+1],那么交换之后c[i]的逆序数减1,而c[i+1]的逆序数不变。显然,空格与相邻棋子的交换不会改变棋局中棋子数列的逆序数的奇偶性。由于最终的逆序数是0,所以当初始状态棋局的棋子数列的逆序数是奇数时无解,偶数时有解。

3.启发式搜索(A*算法)

很多时候,BFS运行得较快,但是它找到的路径明显不是一条好的路径。

0818b9ca8b590ca3270a3433284dd417.png

问题在于BFS是基于贪心策略的,它试图向目标移动尽管这不是正确的路径。由于它仅仅考虑到达目标的代价,而忽略了当前已花费的代价,于是尽管路径变得很长,它仍然继续走下去。1968年发明的A*算法就是把启发式方法(heuristic approaches)如BFS,和常规方法如Dijsktra算法结合在一起的算法。有点不同的是,类似BFS的启发式方法经常给出一个近似解而不是保证最佳解。然而,尽管A*基于无法保证最佳解的启发式方法,A*却能保证找到一条最短路径。在启发式搜索中,我们每次找到当前“最有希望是最短路径”的状态进行扩展。对于每个状态的我们用函数F来估计它是否有希望。

F=G+H

G:就是普通宽度优先搜索中的从起始状态到当前状态的代价,比如在这次的问题中,G就等于从起始状态到当前状态的最少步数。

H:是一个估计的值,表示从当前状态到目标状态估计的代价。

H是由我们自己设计的,H函数设计的好坏决定了A*算法的效率。H值越大,算法运行越快。但是在设计评估函数时,需要注意一个很重要的性质:评估函数的值一定要小于等于实际当前状态到目标状态的代价。否则虽然程序运行速度加快,但是可能在搜索过程中漏掉了最优解。相对的,只要评估函数的值小于等于实际当前状态到目标状态的代价,就一定能找到最优解。所以,在这个问题中我们可以将评估函数设定为1-8八数字当前位置到目标位置的曼哈顿距离之和。

0818b9ca8b590ca3270a3433284dd417.png

成功的秘决在于,它把Dijkstra算法(靠近初始点的结点)和BFS算法(靠近目标点的结点)的信息结合了起来。

以hihoCoder1068为例给出代码:

#include

#include

#include

#include

#include

#include

using namespace std;

int Hash[15];

struct node

{

int f,h,g;

int x,y;

char map[3][3];

friend bool operator

{

if(a.f==b.f) return a.g

return a.f>b.f;

}

};

node start;

bool vis[500000];

int to[4][2]={0,-1,0,1,-1,0,1,0};

int pos[][2]={{0,0},{0,1},{0,2},{1,0},{1,1},{1,2},{2,0},{2,1},{2,2}};

//判断是否有解

int check()

{

int i,j,k;

int s[20];

int cnt=0;

for(i=0;i<3;i++)

{

for(j=0;j<3;j++)

{

s[3*i+j]=start.map[i][j];

if(s[3*i+j]=='x') continue;

for(k=3*i+j-1;k>=0;k--)

{

if(s[k]=='x') continue;

if(s[k]>s[3*i+j]) cnt++;

}

}

}

if(cnt%2) return 0;

return 1;

}

//康托展开

int solve(node a)

{

int i,j,k;

int s[20];

int ans=0;

for(i=0;i<3;i++)

{

for(j=0;j<3;j++)

{

s[3*i+j]=a.map[i][j];

int cnt=0;

for(k=3*i+j-1;k>=0;k--)

{

if(s[k]>s[3*i+j]) cnt++;

}

ans=ans+Hash[i*3+j]*cnt;

}

}

return ans;

}

//计算h值,即曼哈顿距离

int get_h(node a)

{

int i,j;

int ans=0;

for(i=0;i<3;i++)

{

for(j = 0; j<3; j++)

{

if(a.map[i][j]=='x') continue;

int k=a.map[i][j]-'1';

ans+=abs(pos[k][0]-i)+abs(pos[k][1]-j);

}

}

return ans;

}

//启发式搜索

int bfs()

{

memset(vis,0,sizeof(vis));

priority_queue Q;

start.g=0;

start.h=get_h(start);

start.f=start.h;

vis[solve(start)]=true;

if(solve(start)==0) return 0;

Q.push(start);

node next;

while(!Q.empty())

{

node a=Q.top();

Q.pop();

int k_s=solve(a);

vis[k_s]=true;

for(int i=0;i<4;i++)

{

next=a;

next.x+=to[i][0];

next.y+=to[i][1];

if(next.x<0||next.y<0||next.x>2||next.y>2) continue;

next.map[a.x][a.y]=a.map[next.x][next.y];

next.map[next.x][next.y]='x';

next.g+=1;

next.h=get_h(next);

next.f=next.g+next.h;

int k_n=solve(next);

if(k_n==0) return next.g;

if(vis[k_n]) continue;

Q.push(next);

}

}

}

int main()

{

Hash[0]=1;

for(int i=1;i<=9;i++) Hash[i]=Hash[i-1]*i;

int t;

cin>>t;

for(int i=0;i

{

for(int i=0;i<3;i++)

{

for(int j=0;j<3;j++)

{

char a;

cin>>a;

start.map[i][j]=a;

if(a=='0')

{

start.map[i][j]='x';

start.x=i;

start.y=j;

}

}

}

if(!check())

{

cout<

}

else cout<

}

}

你可能感兴趣的:(八数码问题a*算法c语言)