对于这种"求从初始状态到目标状态的步数"的搜索题,BFS是较好的选择.
(4*4)的正方形内每个格子上是数字0或1,要由初始状态移动到目标状态,每次移动只能上下左右四个方向移动,求最少的移动次数
对比两幅图(初始状态和目标状态),对于数字一样的格子(同为0或者同为1),在两幅图上都赋值为0;然后对于初始状态图中剩下未匹配的点(即该点在两幅图中的值不同),开一个结构体记下横纵坐标.
四维数组(f[i][j][k][l])记录的是([i,j])和([k,l])两点之间的曼哈顿距离(即横纵坐标之差的绝对值的和),表示([i,j])要移动到([k,l])需要多少次移动.
int sum,ans=1e9;
int a[5][5],b[5][5],f[5][5][5][5],visit[5][5];
struct A{
int x,y;
}point[30];
//cnt:搜索过程中匹配好的点的数量
//sum:先前未匹配的点的数量
//ans,val:移动步数
void dfs(int cnt,int val){
if(cnt>sum){ans=min(ans,val);return;}
//全匹配好了,就比较大小求出最小值
for(int i=1;i<=4;i++)
for(int j=1;j<=4;j++){
if(f[point[cnt].x][point[cnt].y][i][j]&&!visit[i][j]){
visit[i][j]=1;
dfs(cnt+1,f[point[cnt].x][point[cnt].y][i][j]+val);
visit[i][j]=0;
}
//如果当前要匹配的点[x,y]与搜索到的点[i,j]可以发生匹配,且[i,j]没和其它点匹配过
}
}
int main(){
for(int i=1;i<=4;i++)
for(int j=1;j<=4;j++){
char ch;cin>>ch;
a[i][j]=ch-'0';
}
for(int i=1;i<=4;i++)
for(int j=1;j<=4;j++){
char ch;cin>>ch;
b[i][j]=ch-'0';
if(a[i][j]==b[i][j])
a[i][j]=b[i][j]=0;
}
//经过上述步骤:把两个图中相同的1变为0后
//此时b图中的1对应在a图中一定是0(即是需要匹配,需要被交换位置的0)
for(int i=1;i<=4;i++)
for(int j=1;j<=4;j++){
if(a[i][j]==1){
point[++sum].x=i;point[sum].y=j;
for(int k=1;k<=4;k++)
for(int l=1;l<=4;l++)
if(b[k][l]==1)
f[i][j][k][l]=abs(l-j)+abs(k-i);
}
}
//所以这里实际上是在a图中把所有1与所有需要匹配的0
//两两之间的距离算出来存在f数组中
//为什么要算距离?因为这些1和0两两需要调换位置
//而我们无法预测某个1一定会和哪个0交换位置
dfs(1,0);
printf("%d
",ans);
return 0;
}
本题还可以把(4*4)的格子(状态)通过哈希状态压缩,不同的局面最多只有(2^{16})=65536个,然后跑BFS.
在(3×3)的棋盘上,摆有八个棋子,每个棋子上标有1至8中的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。
要求解的问题是:给出一种初始状态和目标状态(目标状态为123804765),找到一种最少步骤的移动方法,实现从初始状态到目标状态的转变。
发现本题只有(3*3)的格子,也就是只有9位数,那就可以直接用int存下状态.
为了优化,既然我们知道了初始状态和目标状态,不妨跑双向广搜.
int st,ed=123804765;
int a[5][5];
int dx[4]={1,-1,0,0},
dy[4]={0,0,1,-1};
map<int,int> ans,visit;
queue<int> q;
void bfs(){
q.push(st);q.push(ed);
//把初始状态和目标状态都放入队列中
//下面赋的初值好好思考一下
ans[st]=0,ans[ed]=1;
//就算是双向广搜,也不可能同时走两步(拓展两个状态)
//一定是先拓展一个状态,再对另一状态进行拓展
//故设初始状态的ans值为0,则目标状态的ans值就为1
visit[st]=1,visit[ed]=2;
//不同的取值是为了区分是哪个状态拓展的
while(!q.empty()){
int u=q.front(),now=u,zeroi,zeroj;
q.pop();
for(int i=3;i>=1;i--)
for(int j=3;j>=1;j--){
a[i][j]=now%10;
now/=10;
if(a[i][j]==0){
zeroi=i;
zeroj=j;
}
}
//把int存的状态转为3*3的格子中,同时记录0所在的位置
for(int i=0;i<=3;i++){
int xx=dx[i]+zeroi;
int yy=dy[i]+zeroj;
if(xx<1||xx>3||yy<1||yy>3)
continue;
swap(a[xx][yy],a[zeroi][zeroj]);
//找到0位置周围一个合法的位置进行位置交换
now=0;
for(int j=1;j<=3;j++)
for(int k=1;k<=3;k++){
now=now*10+a[j][k];
}
//位置交换后,又把3*3的状态转到int中存下
if(visit[now]==visit[u]){
swap(a[xx][yy],a[zeroi][zeroj]);
continue;
}
//如果now这个状态之前已经访问过,
//且与当前出队状态u是同一个状态拓展出的,则不合法
if(visit[now]+visit[u]==3){
printf("%d
",ans[now]+ans[u]);
exit(0);
}
//如果now这个状态之前已经访问过,
//且1+2=3:
//即一个是初始状态拓展而来,一个是目标状态拓展而来
//则说明双向搜索途中相遇了,答案也就出来了
//通过这两个if语句的判断,理解visit赋值不同的作用
ans[now]=ans[u]+1;
visit[now]=visit[u];
q.push(now);
//既不是非法状态,又不是答案状态,则记录信息,继续入队
swap(a[xx][yy],a[zeroi][zeroj]);
//最后记得把格子复原(因为当前状态已存入int中入队)
}
}
int main(){
st=read();
if(st==ed){
puts("0");
return 0;
}//特判一下
bfs();
return 0;
}