以下内容仅是我个人对八数码问题和A*寻路算法的理解,因为我是菜鸟一个,所以写的比较通俗。八数码问题也称为九宫问题。在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的某一数字,不同棋子上标的数字不相同。棋盘上还有一个空格,与空格相邻的棋子可以移到空格中。要求解决的问题是:给出一个初始状态和一个目标状态,找出一种从初始转变成目标状态的移动棋子步数最少的移动步骤。
A*算法:
A*算法是一种在静态路网中求解最短路径的有效算法,通俗地讲,它不是像深度优先搜索算法和广度优先搜索算法一样的傻瓜式的埋头搜索,它是先对当前的情况进行分析,得到最有可能的一个分支,然后在该分支上进行扩展,然后将扩展的结果放在之前的大环境中进行比较,再选取最有可能的分支进行扩展,直到找到最终状态。A*算法的核心是估价函数的选取(通俗的说就是对当前情况的评价方式的选取,通过什么方式选取的分支才是最有可能离最终状态最近的分支)。 公式表示为: f(n)=g(n)+h(n), 其中 f(n) 是从初始点经由节点n到目标点的估价函数, g(n) 是在状态空间中从初始节点到n节点的实际代价, h(n) 是从n到目标节点最佳路径的估计代价。用A*算法思想分析八数码问题:
在本题目中,初始状态为任意一个可能的状态。终止状态为按顺序排列的八个数,空白用0表示,即: 在八数码问题中,节点即为一个状态,我们令g(n)为从开始节点到当前节点所经过的实际步数(即深度),h(n)为从该节点到最终节点必须至少要的步数的估计(在估算h(n)时,我们忽略其他的数字对该数字到其最终位置的影响,所以h(n)只是其最小步数的下限),f(n)=h(n)+g(n)。八数码问题的C++代码:
#include<iostream> #include <stdlib.h> using namespace std; struct Node //节点 { int s[3][3]; int f,g; //f为从当前节点到最终节点的最少代价,g为从初始状态到当前状态经过的步数 Node * next,*previous; }; class EP { private: Node *open,*close,*bestnode,*successor,*initial;//open为没有扩展过的能到达的状态的链表头,close为扩展过的的节点的链表表头//bestnode为当前评价最好的节点,即g+f最小的节点//successor为有当前节点经过一步操作可能到达的节点//initial为初始节点int errorsum(int[3][3]); //计算f的函数,即节点上的所有数字到其最终位置的哈密顿距离的和 void addnode(Node* &,Node*); //向表中加入节点 void removenode(Node *&,Node*); //从表中删除节点 bool getmove(char,Node*); //求由当前节点经过一步操作可能到达的下一个节点 void update(Node*); //数据更新(因为到达一个节点可不同的路径,我们只要记住最短的路径即可,所以//当出现的节点和之前评价过的节点相同且当前的代价要比之前代价小时,我们要放//弃之前的评价更新为现在的评价。 int go(); //求解入口 bool iscontain(Node*,Node*,Node*); //求表中是否含有某节点 void show(Node*); //输出最终路径 public: EP(); //构造函数 }; EP::EP() { open=new Node; open->previous=NULL; open->next=NULL; close=new Node; close->previous=NULL; close->next=NULL; for(int i=0;i<3;i++) for(int j=0;j<3;j++) open->s[i][j]=close->s[i][j]=0; cout<<"输入初始状态:"<<endl; initial=new Node; initial->previous=NULL; initial->next=NULL; for(int i=0;i<3;i++) for(int j=0;j<3;j++) cin>>initial->s[i][j]; initial->f=errorsum(initial->s); initial->g=0; addnode(open,initial); if(go()==-1) cout<<"无解"<<endl; else show(bestnode); } int EP::errorsum(int s[3][3]) { int sum=0; for(int i=0;i<3;i++) for(int j=0;j<3;j++) if(s[i][j]!=0) sum+=(abs((s[i][j]-1)/3-i)+abs((s[i][j]-1)%3-j)); return sum; } void EP::addnode(Node* &h,Node*p) { Node *q=h->next; if(q) { if((p->f+p->g)<(q->f+q->g)) { p->next=q; h->next=p; } else { while(q->next) { if((p->f+p->g)<(q->f+q->g)&&(q->next->f+q->next->g)>=(p->f+p->g)) { p->next=q->next; q->next=p; break; } q=q->next; } if(q->next=NULL) q->next=p; } } else h->next=p; } void EP::removenode(Node* &h,Node*p) { Node*q=h; while(q->next) { if(q->next==p) { q->next=p->next; if(q->next==NULL) return; } q=q->next; } } int EP::go() { while(1) { if(open->next==NULL) return -1; bestnode=open->next; removenode(open,bestnode); addnode(close,bestnode); if((bestnode->f==0)) return 1; else { successor=new Node; if(getmove('d',successor)) update(successor); successor=new Node; if(getmove('u',successor)) update(successor); successor=new Node; if(getmove('l',successor)) update(successor); successor=new Node; if(getmove('r',successor)) update(successor); } } } bool EP::getmove(char c,Node*p) { int i0,j0; for(int i=0;i<3;i++) for(int j=0;j<3;j++) { p->s[i][j]=bestnode->s[i][j]; if(p->s[i][j]==0) i0=i,j0=j; } switch(c) { case 'u':if(i0<2){p->s[i0][j0]=p->s[i0+1][j0],p->s[i0+1][j0]=0;return true;}break; case 'd':if(i0>0){p->s[i0][j0]=p->s[i0-1][j0],p->s[i0-1][j0]=0;return true;}break; case 'l':if(j0<2){p->s[i0][j0]=p->s[i0][j0+1],p->s[i0][j0+1]=0;return true;}break; case 'r':if(j0>0){p->s[i0][j0]=p->s[i0][j0-1],p->s[i0][j0-1]=0;return true;}break; } return false; } void EP::update(Node *p) { Node* old; p->previous=bestnode; p->next=NULL; p->g=bestnode->g+1; p->f=errorsum(p->s); if(iscontain(open,p,old)) { if(p->g<old->g) { old->g=p->g; removenode(open,old); addnode(open,old); } } else { if(iscontain(close,p,old)) { if(p->g<old->g) { old->previous=bestnode; old->g=p->g; } } else { p->f=errorsum(p->s); addnode(open,p); } } } bool EP::iscontain(Node *h,Node*p,Node*old) { Node* q=h; while(q->next) { int f=0; for(int i=0;i<3;i++) for(int j=0;j<3;j++) if(q->next->s[i][j]!=p->s[i][j]) f=1;break; if(f==0) { old=q->next; return true; } else q=q->next; } return false; } void EP::show(Node* h) { Node *p=h; while(p!=initial) p->previous->next=p,p=p->previous; p=initial; while(p!=h->next) { cout<<"---------------------"<<endl; for(int i=0;i<3;i++) { for(int j=0;j<3;j++) cout<<p->s[i][j]<<" "; cout<<endl; } cout<<"f="<<p->f<<" g="<<p->g<<endl; p=p->next; } } int main() { EP e; return 0; }需要注意的是:
1.open是一个单向链表,存储的是可以由初始节点由若干步达到的可能节点(我们忽略节点Node中的previous),其是按照代价估计(f+g)从小 到大排序的。
2.close的结构比较特殊,如果从头到尾顺序看(忽略节点Node中的previous)是一个单向链表,从尾向前看(忽略节点Node中的next),是一个倒向的树。(因为要输出最终的节点转移过程,所以必须要记录每一个节点是由哪一个节点扩展来的,虽然一个节点可以由很多节点扩展而来,但是别忘了我们之前说的,只记录从初始节点到当前节点最短的那条路径,update函数所做的事),虽然结构有点混乱,但是顺看的链表和倒看的倒树并不相互影响,链表只是为了方便节点的管理。
3.最后输出结果的过程弄乱了close的结构,只保留了我们要输出的这条路径线。
该代码段的缺点:
内存回收比较难,内存都是只申请不释放。
我们求得的只是较短的路径,但未必是最短的路径。
时间: 2024-10-09 22:20:49