路径寻找问题:八数码问题

来源:互联网 发布:java项目部署到tomcat 编辑:程序博客网 时间:2024/06/08 18:23

在现实问题中,很多问题都可以归节为图的遍历,但这些问题中的图却不是事先给定,从程序读入的,而是有程序动态生成的,称为隐式图。路径寻找问题可以归结为隐式图的遍历,你的任务是找到一条从初始状态到终止状态的最优路径。下面我们来看路径寻找问题的经典问题:八数码问题。

题意:如下如所示,a,b分别表示两种状态,每个九宫格中的数字都是0~8共9个数字的任意一种排列,现在要把算出a状态移动到b状态的最佳步骤(移动步数最少)。移动的规则是0方块与上下左右任意一块互换位置。其实这个问题和我们经常玩的拼图游戏是一样的。0就代表空。

题目链接:https://vjudge.net/problem/HIT-1868


分析:不难把八数码问题归结为图上的最短路问题。无权图上的最短路问题都可以用bfs求解。直接暴搜,虽然效率不高,但是可以求出结果。但是存在一个问题,就是bfs的判重问题。对于图来说,搜索的时候一定要标记已经访问过的结点,刚开始我的想法是用set集合存储一个字符串(比如上面左图字符串为238016745),访问过的结点都将其放到集合里,每次判重的时候只需要访问set,看里面有没有这个结点即可,但是这个问题本来就存在效率问题,STL效率将会更低。看了刘汝佳老师的算法竞赛入门经典。我又学到了一些方法来解决这个问题。

下面我将几种方法代码贴下面。几种标记方法原理可参见:http://blog.csdn.net/mylinchi/article/details/53714235,这位大牛写的很清楚,我就不详细介绍了。

1.设计一套排列的编码和解码问题。

#include<cstdio>#include<iostream>#include<algorithm>#include<cstring>#include<vector>using namespace std;typedef int state[9];const int maxstate=600000;const int INF=0x7fffffff;const int maxn=30;const int dir[4][2]= {{1,0},{-1,0},{0,1},{0,-1}};state st[maxstate],goal;int dist[maxstate];int vis[maxstate];int fact[10];int head,rear;void init() {    fact[0]=1;    for(int i=1; i<9; i++)        fact[i]=fact[i-1]*i;//求阶乘}int try_insert(int s) { //判断结点是否访问    int sum=0;    for(int i=0; i<9; i++) {        int cnt=0;        for(int j=i+1; j<9; j++)            if(st[s][j]<st[s][i]) cnt++;         sum+=fact[8-i]*cnt;    }    if(vis[sum]) return 0;    vis[sum]=1;    return 1;}int bfs() {     while(head<rear) {        state& s=st[head];        if(memcmp(goal,s,sizeof(s))==0) return head;        int z;        for(z=0; z<9; z++) if(!s[z]) break;        int x=z/3,y=z%3;        for(int i=0; i<4; i++) {            int new_x=x+dir[i][0];            int new_y=y+dir[i][1];            int new_z=new_x*3+new_y;            if(new_x<0||new_x>=3||new_y<0||new_y>=3) continue;            state& t=st[rear];            memcpy(&t,&s,sizeof(s));            t[new_z]=s[z];            t[z]=s[new_z];            if(try_insert(rear)) {                dist[rear]=dist[head]+1;                rear++;            }        }        head++;    }    return 0;}int main() {    //freopen("in.txt","r",stdin);    int T;    cin>>T;    init();    while(T--) {        for(int i=0; i<9; i++) cin>>st[1][i];        for(int i=0; i<9; i++) cin>>goal[i];        head=1;rear=2;        memset(vis,0,sizeof(vis));        memset(dist,0,sizeof(dist));        if(bfs()) cout<<dist[head]<<endl;        else cout<<-1<<endl;    }    return 0;}

2.使用哈希技术,设计哈希函数。

#include<cstdio>#include<iostream>#include<algorithm>#include<cstring>#include<vector>using namespace std;typedef int state[9];const int maxstate=600000;const int INF=0x7fffffff;const int maxn=30;const int hashsize = 1000003;const int dir[4][2]= {{1,0},{-1,0},{0,1},{0,-1}};state st[maxstate],goal;int dist[maxstate];int fact[10];int head,rear;int myhead[hashsize];int mynext[maxstate];void init() {    memset(myhead, 0, sizeof(myhead));    memset(mynext, 0, sizeof(mynext));}int myhash(int src) {      int val = 0;    for (int i = 0; i < 9; i++)        val = 10 * val + st[src][i];    return val%hashsize;}bool try_insert(int s) {    int hcode = myhash(s);    int u = myhead[hcode];    while (u) {        if (memcmp(st[u],st[s],sizeof(st[s])) == 0) return false;        u = mynext[u];    }    mynext[s] = myhead[hcode];    myhead[hcode] = s;    return true;}int bfs() {    init();    while(head<rear) {        state& s=st[head];        if(memcmp(goal,s,sizeof(s))==0) return head;        int z;        for(z=0; z<9; z++) if(!s[z]) break;        int x=z/3,y=z%3;        for(int i=0; i<4; i++) {            int new_x=x+dir[i][0];            int new_y=y+dir[i][1];            int new_z=new_x*3+new_y;            if(new_x<0||new_x>=3||new_y<0||new_y>=3) continue;            state& t=st[rear];            memcpy(&t,&s,sizeof(s));            t[new_z]=s[z];            t[z]=s[new_z];            if(try_insert(rear)) {                dist[rear]=dist[head]+1;                rear++;            }        }        head++;    }    return 0;}int main() {    //freopen("in.txt","r",stdin);    int T;    cin>>T;    while(T--) {        for(int i=0; i<9; i++) cin>>st[1][i];        for(int i=0; i<9; i++) cin>>goal[i];        head=1;rear=2;        memset(dist,0,sizeof(dist));        if(bfs()) cout<<dist[head]<<endl;        else cout<<-1<<endl;    }    return 0;}


不过可惜,这两种写法都超时,鉴于自己水平有限,读者课参考下面链接AC代码。

http://blog.csdn.net/niuox/article/details/8960833