所谓八数码,就是类似上图的这种游戏。规则是给定你起始状态和终止状态,问最少要几步能从起始状态变换到终止状态。对于这道题,算法思想不难想,就是广搜,把一个棋局看成一个状态,每次移动产生新的状态,一个个遍历直至找到目标状态。如果直接这样做会出现Memory limited exceeded
这样就遇到了第一个问题——剪枝,对于空格(也就是我们可移动的地方)可以有四个方向移动(上下左右),但是当我们把空格移到某个地方之后,那么在这个新的地方又有一种选择是再移回原来的地方,这样就会多出许多不必要的步骤。所以我们需要剪枝,第一个算法——康托展开:判断这个状态是否走过。首先需要一维与二维的来回转换——对于这种转换,一维转二维 行:pos/n,列:pos%n;二维转一维:列+n×行。其次,康托展开公式,对于一种排列,判断他是全排列中的第几个。
X=a[n](n-1)!+a[n-1](n-2)!+…+a[2]*1!+a[1]*0!;
a[i]表示原数中的第i位在当前未出现的元素中排第几位(从0开始)。
第二个算法:广搜(大体思路简单,但有许多细节)这里以洛谷中的P1379为例
#include <bits/stdc++.h>
using namespace std;
struct s{int code[9];int index;};
int start[9]={1,2,3,8,0,4,7,6,5};int goal[9];
int movexy[2][4]={0,-1,0,1,-1,0,1,0};
int flag[362881]={0};
int m(int n)//阶乘函数
{
int sum=1;
while(n>0){sum*=n;n--;}
return sum;
}
bool countor(int x[])
{
int sum=0;
int ant=8;
for(int a=0;a<9;a++)
{
int ans=0;
for(int b=a+1;b<9;b++)
{
if(x[a]>x[b])ans++;
}
sum+=ans*m(ant);
ant--;
}
if(!flag[sum]){//判断这个状态是否出现过
flag[sum]=1;
return 1;
}
else return 0;
}
int bfs()
{
queue<s>q;
s temp;
memcpy(temp.code,start,sizeof(start));
temp.index=0;
countor(temp.code);//表明第一个状态已经出现了
q.push(temp);
while(!q.empty())
{
temp=q.front();
if(memcmp(temp.code,goal,sizeof(goal))==0)
return temp.index;
q.pop();
int z;
for(z=0;z<9;z++)
{
if(temp.code[z]==0)break;
}
int tempx=z%3;
int tempy=z/3;
for(int a=0;a<4;a++)
{
int temp_x,temp_y;
temp_x=tempx+movexy[1][a];
temp_y=tempy+movexy[0][a];
if(temp_x>=0&&temp_x<3&&temp_y>=0&&temp_y<3)
{
s newtemp;//建立走完之后的新状态
int dex=temp_x+3*temp_y;
memcpy(&newtemp,&temp,sizeof(struct s));
swap(newtemp.code[z],newtemp.code[dex]);
newtemp.index++;
if(countor(newtemp.code))
q.push(newtemp);
}
}
}
return 0;
}
int main()
{
string s;
cin>>s;
for(int a=0;a<9;a++)
{
goal[a]=s[a]-'0';
}
int ans=bfs();
cout<<ans<<endl;
return 0;
}