经典八数码问题,给一个3*3的方阵,上面写着0~8的数字,
求把方阵变换成另一个方阵所需最小步数
题目链接: 八数码难题 洛谷P1379
考虑暴力搜索,一共有9的9次方种状态,超时
A*算法:相比于盲目搜索,每次朝着离目标状态最接近的方向搜索
定义估价函数 f(n)= h(n)+ g(n)
我们要搜索最小步数,因此我们希望 f(n)最小,h(n)是启发函数,是人为构造的,表示当前状态距离目标状态预期需要的最小步数,而 g(n)表示从初始状态到目前走了多少步数,h(n)经过思考后,在本题中可以定义为每个不同数码在当前状态距离目标状态曼哈顿距离的和。
在具体实现A*时,我们按照一般bfs的思路,把起始状态入队,循环取出队列中f(n)最小的状态(这里可以用优先队列优化),然后把该状态可能的后续状态都入队,重复上述步骤,直到取出的状态为最终状态。
不会bfs的同学戳这里:
bfs与dfs详解
不会优先队列(堆)的同学戳这里:
优先队列详解
具体实现如下(加了可视化操作,比原题略有改动)
#include<iostream> #include<queue> #include<utility> #include<map> #include<cmath> #include<windows.h> #define mp(x,y) make_pair(x,y) using namespace std; //输入两个长度为9的数字串,分别代表初始状态和目标状态 typedef struct node{//定义节点为目前棋盘状态 pair<int,int> center; int g[4][4]; }node; bool operator == (node x,node y){用于比较节点是否相同 for(int i=1;i<=3;i++) { for(int j=1;j<=3;j++) { if(x.g[i][j]!=y.g[i][j])return 0; } } return 1; } bool operator < (node x,node y){//用于查找节点 for(int i=1;i<=3;i++) { for(int j=1;j<=3;j++) { if(x.g[i][j]<y.g[i][j])return 1; else if(x.g[i][j]>y.g[i][j])return 0; } } return 0; } void init_node(node &x){//初始化节点 for(int i=0;i<=3;i++) { for(int j=0;j<=3;j++) { x.g[i][j]=0; } } } char o1[15],o2[15];//输入初始状态与目标状态 node s,e; priority_queue< pair<int,node> > q; map<node,int> g; //用于记录状态是否出现过,避免重复搜索 map<node,node> pre; //记录前驱节点 int dx[4]={-1,0,1,0};//搜索的四个方向 int dy[4]={0,1,0,-1}; int cal_h(node x)//计算启发函数(两个状态各个数位曼哈顿距离的和) { int sum=0; pair<int,int> h[9];//预处理优化 for(int i=1;i<=3;i++) { for(int j=1;j<=3;j++) { h[x.g[i][j]]=mp(i,j); } } for(int i=1;i<=3;i++) { for(int j=1;j<=3;j++) { sum+=abs(i-h[e.g[i][j]].first)+abs(j-h[e.g[i][j]].second); } } return sum; } void print_mp(node x)//输出当前状态 { system("cls"); for(int i=1;i<=4;i++)cout<<"\n"; for(int i=1;i<=3;i++) { for(int j=1;j<=6;j++)cout<<"\t"; for(int j=1;j<=3;j++) { cout<<x.g[i][j]<<" "; } cout<<"\n\n"; } Sleep(1000); } void print(node x)//递归输出 { if(x==s)print_mp(x); else{ print(pre[x]); print_mp(x); } } int main() { cin>>o1+1; cin>>o2+1; init_node(s);//将输入转化为节点状态 for(int i=1;i<=9;i++) { s.g[(i+2)/3][(i-1)%3+1]=o1[i]-'0'; if(s.g[(i+2)/3][(i-1)%3+1]==0)s.center=mp((i+2)/3,(i-1)%3+1); } init_node(e); for(int i=1;i<=9;i++) { e.g[(i+2)/3][(i-1)%3+1]=o2[i]-'0'; if(e.g[(i+2)/3][(i-1)%3+1]==0)e.center=mp((i+2)/3,(i-1)%3+1); } //广搜,优先队列优化 g[s]=0; q.push(mp(-(cal_h(s)+g[s]),s)); while(!q.empty()) { node tmp=q.top().second; q.pop(); if(tmp==e)break; if(g[tmp]==25)//如果搜索的次数过多,结束搜索 { cout<<"-1"<<endl; return 0; } int x=tmp.center.first; int y=tmp.center.second; for(int i=0;i<=3;i++) { int xx=x+dx[i]; int yy=y+dy[i]; if(xx>=1&&xx<=3&&yy>=1&&yy<=3) { node u=tmp; swap(u.g[x][y],u.g[xx][yy]); if(g.find(u)==g.end())//如果没搜索过该状态,将其入队 { u.center=mp(xx,yy); g[u]=g[tmp]+1; pre[u]=tmp; q.push(mp(-(cal_h(u)+g[u]),u)); } } } } print(e); return 0; }
A*练习题:
骑士精神 洛谷P2324 A*经典题目
k短路 洛谷P4467 比较有难度的题目,需要结合其他算法
转载请注明出处