P1379 八数码难题
题目描述
在 3 × 3 3\times 3 3×3 的棋盘上,摆有八个棋子,每个棋子上标有 1 1 1 至 8 8 8 的某一数字。棋盘中留有一个空格,空格用 0 0 0 来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为 123804765 123804765 123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。
输入格式
输入初始状态,一行九个数字,空格用 0 0 0 表示。
输出格式
只有一行,该行只有一个数字,表示从初始状态到目标状态需要的最少移动次数。保证测试数据中无特殊无法到达目标状态数据。
输入输出样例 #1
输入 #1
283104765
输出 #1
4
说明/提示
样例解释

图中展示了样例当中从初始状态到目标状态的一种方案,共需要 4 4 4 步。
并且可以证明,不存在更优的策略。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
struct coor{int x,y;};//点的坐标
struct node{
string ss,//起始布局
se="123804765"; //目标布局
coor poss[9],//起始布局,各数字的位置
pose[9];//目标布局
int id,//哈希值,用于标记
dis,//当下布局到目标布局的曼哈顿距离,纵横差
step,//当前已经移动的步数
x,y;//当前布局0(空格)所在坐标
node(){
step=0;
se="123804765";
for(int i=0;i<9;i++)pose[se[i]-'0'].x=i/3,pose[se[i]-'0'].y=i%3;//计算目标布局各数字坐标
}
void set(string s){//初始化,赋予初始布局
ss=s;
for(int i=0;i<9;i++){
poss[ss[i]-'0'].x=i/3,poss[ss[i]-'0'].y=i%3;//明确起始布局各数字坐标
if(ss[i]=='0')x=i/3,y=i%3;//0(空格)所在坐标
}
m_id();m_dis();//计算哈希值和曼哈顿距离和
}
void move(int sx,int sy,int ex,int ey){//当前数字移到空格位置
poss[ss[sx*3+sy]-'0'].x=ex,poss[ss[sx*3+sy]-'0'].y=ey,//当前数字的坐标变成空格的坐标
poss[0].x=sx,poss[0].y=sy,//空格移到对应数字位置
swap(ss[sx*3+sy],ss[ex*3+ey]);//对调字符串中0和当前数字的位置
x=sx,y=sy;//空格的新位置
m_id();m_dis();//
step++;
}
void m_id(){//计算哈希值,字符串转整数
id=0;
for(int i=0;i<9;i++)id=id*10+ss[i]-'0';
}
void m_dis(){//计算各数字到目标布局的曼哈顿距离和
dis=0;
for(int i=0;i<9;i++)dis+=abs(poss[i].x-pose[i].x)+abs(poss[i].y-pose[i].y);
}
bool operator<(const node b)const{return step+dis>b.step+b.dis;}//大顶堆默认<,现需要逆序,所以相反成>
//a*算法的核心,相对于宽搜、dijstra算法的区别
//宽搜没有优先队列,
//dijkstra的优先队列只看已走了多少
}sta1,sta2;//移动前后布局
string s;
priority_queue<node> q;//优先队列,默认大顶堆,现在是逆序小顶堆,每次找最小成本状态
unordered_set<int> vis;//用哈希值记住已遍历布局
int sx,sy,ex,ey,
d[4][2]={{0,-1},{-1,0},{0,1},{1,0}};
int main(){
//freopen("data.cpp","r",stdin);
cin>>s;
sta1.set(s);
q.push(sta1);vis.insert(sta1.id);
while(!q.empty()){
sta1=q.top();q.pop();
if(sta1.dis==0){cout<<sta1.step;return 0;}
sx=sta1.x,sy=sta1.y;
for(int i=0;i<4;i++){
ex=sx+d[i][0],ey=sy+d[i][1];
if(ex<0||ex>2||ey<0||ey>2)continue;
sta2=sta1;
sta2.move(ex,ey,sx,sy);
if(vis.count(sta2.id))continue;
vis.insert(sta2.id);
q.push(sta2);
}
}
cout<<-1;
return 0;
}
搜索算法横向比较
1968 年,Hart、Nilsson、Raphael 三个人提出了a算法,叫一类带启发式的最佳优先搜索算法。叫Algorithm A→ 就是「A 类算法」,3星4星启发式搜索算法。
几种搜索都是宽搜DFS,标记遍历,访问队列,一直扩散搜索范围。走到哪算哪!
Dijkstra是用了优先队列,优先选择已走步数最少的,如果节点权值一样就退化成DFS。走最少的路,但管不了方向。
A的优先队列优先选择:已走步数+目标预估距离,有方向。最最少的路,而且直奔方向。