挑战ACM迷宫(DFS,BFS,BFS+优先队列)

来源:互联网 发布:unity3d人物模型下载 编辑:程序博客网 时间:2024/06/05 14:44

题目描述
如下图所示的是一个由程序设计题目组成的ACM迷宫。迷宫的左上角是入口,右下角是出口。迷宫中每一个格子都有一个程序设计题目,挑战者要AC该题目后才能通过,大于0的数字表示AC该题目所需的最短时间。数字如果是0表示是陷阱,进去了就出不来。现在的问题是:求挑战者从入口到出口所需的最短时间。
这里写图片描述

输入
第一行有一个整数:T,代表有多少组测试数据。
接下来T行,每行有两个整数(都是四位数),第一个是初状态,第二个是目标状态。
输出
每组数据输出一个整数,占一行。
样例输入
10
1 0 2 1 2 3 4 2 2 5
2 1 0 1 3 2 5 7 2 1
1 1 1 0 1 1 1 3 2 3
1 2 1 1 0 1 1 1 2 3
1 1 2 0 2 0 2 3 2 3
2 2 2 2 3 2 0 2 3 2
3 2 2 2 1 1 1 0 1 1
0 1 1 3 0 1 1 2 3 2
2 0 1 1 2 2 2 2 2 2
3 2 3 2 3 2 3 2 3 2
5
1 2 1 2 5
1 3 2 4 5
2 1 0 2 5
2 1 1 2 1
2 4 1 1 2
样例输出
min=29
min=11

思路:开始一直认为这个题用DFS再加上一个剪枝就可以AC,但是可能因为搜索的深度太大,导致时间超限,剪枝看起来就不够强力。
其实,迷宫可以看做无向图(隐式图),调用BFS记录起点到可到达的各个点的最少步数,然后将起点到终点的步数即为所求ans。
但在写这题时,出现了一个“意外”的情况,那就是INF的大小设为多少的问题,其实按照思路,要求起点到各个点的最少步数,其初值设为一个足够大的值就可以,但是当用memset(num,INF,sizeof(num))时可能会出错,原因是 memset()是按字节操作的;一般只置0或者-1,置其他的值不会得到理论值!!!
详见:http://blog.csdn.net/feng_zhiyu/article/details/75529979

代码如下:
1.BFS

#include<cstdio>#include<queue>#include<cstring>#include<algorithm>using namespace std;const int INF=0x3f3f3f3f;///常常将这个设定为最大值int a[105][105];int dxy[4][2]= {1,0,0,-1,-1,0,0,1};int n,ans,num[105][105];struct Node{    int x,y,cnt;};bool in(int a,int b){    if(a>=0&&a<n&&b>=0&&b<n)        return true;    return false;}bool dx(Node p){    if(p.cnt<num[p.x][p.y])    {        num[p.x][p.y]=p.cnt;        return true;    }    return false;}void bfs(){    Node tou,p;    queue<Node>que;    tou.x=tou.y=0;    tou.cnt=a[0][0];    que.push(tou);    while(!que.empty())    {        tou=que.front();        que.pop();        if(tou.x==n-1&&tou.y==n-1&&((tou.cnt<ans)||(ans==INF)))        {            ans=tou.cnt;            continue;        }        if(tou.cnt>ans&&ans!=INF)            continue;        for(int i=0; i<4; i++)        {            p=tou;            p.x+=dxy[i][0],p.y+=dxy[i][1];            if(in(p.x,p.y))            {                p.cnt+=a[p.x][p.y];                if(a[p.x][p.y]!=0&&p.cnt<ans&&dx(p))                    que.push(p);                //vis[nx][ny]=1;            }        }    }}int main(){    //freopen("E:/in.txt","r",stdin);    while(~scanf("%d",&n))    {        ans=INF;        ///memset(num,INF,sizeof(num)); 使用此方法初始化时不能置0或-1以外的数,否则可能因此导致答案错误        // memset(a,0,sizeof(a));        for(int i=0; i<n; i++)            for(int j=0; j<n; j++)            {                scanf("%d",&a[i][j]);                num[i][j]=INF;            }        bfs();        printf("min=%d\n",ans);    }    return 0;}

这里写图片描述
2.BFS+优先队列
这个思路其实在之前就有想过,但是之前写的题一直都是用BFS和DFS,把方法想死 了。 今天看见别人写出了来了,现在想想,优先队列,每次优先取最小的,求出来的和一定是最优的。

#include<cstdio>#include<queue>#include<cstring>#include<algorithm>using namespace std;const int N=105;const int INF=0x3f3f3f3f;int n,a[N][N],vis[N][N];int ans;int dxy[4][2]= {0,1,0,-1,1,0,-1,0};struct Node{    int x,y;    int cnt;    bool operator < (const Node &m)const//重载<运算符    {        return cnt>m.cnt;///按小到大排列    }};bool in(int x,int y){    if(x>=0&&x<n&&y>=0&&y<n)        return true;    return false;}void bfs(){    Node now,next;    now.x=0,now.y=0;    now.cnt=a[0][0];    vis[0][0]=1;    priority_queue<Node>pq;    pq.push(now);    while(!pq.empty())    {        now=pq.top();        pq.pop();        if(now.x==n-1&&now.y==n-1)        {            //if(now.cnt<ans)            ans=now.cnt;            return ;        }        for(int i=0; i<4; i++)        {            int nx=now.x+dxy[i][0],ny=now.y+dxy[i][1];            if(in(nx,ny)&&!vis[nx][ny]&&a[nx][ny])            {                vis[nx][ny]=1;                next.x=nx;                next.y=ny;                next.cnt=now.cnt+a[nx][ny];                pq.push(next);            }        }    }}int main(){    //freopen("E:\\in.txt","r",stdin);    while(~scanf("%d",&n))    {        for(int i=0; i<n; i++)            for(int j=0; j<n; j++)                scanf("%d",&a[i][j]);        memset(vis,0,sizeof(vis));        ans=INF;        bfs();        printf("min=%d\n",ans);    }    return 0;}

这里写图片描述
BFS+优先队列的解法相比BFS搜索并且比较起点到终点的每条路径大小这种方法用时少了很多,因为优先队列,只要到达了终点就一定是最优的,就结束了队列的扩展;而另外一种方法要把所有从起点到终点的路径遍历完。

3.DFS 时间超限了,还是先把代码放上来把,最近做的搜索题几乎很多题有多种解法,我想应该可以吧
做了一些题后,发现了记忆搜索在深搜中比较常用,下面的DFS代码(已经AC)就用到了
DFS重要的是3个剪枝,没找好就一直超时

#include<cstdio>#include<algorithm>#include<cstring>using namespace std;const int N=105;const int INF=0x3f3f3f3f;int n,a[N][N],vis[N][N];int t[N][N],dxy[4][2]={0,1,1,0,0,-1,-1,0};int ans;bool in(int x,int y){    if(x>=0&&x<n&&y>=0&&y<n)        return true;    return false;}void dfs(int x,int y,int time){    if(x==n-1&&y==n-1)    {        ans=min(ans,time);        return ;    }    if(time>=ans) return;//剪枝1,到(x,y)的时间已经大于最少时间    if(time>=t[x][y]) return;//剪枝2,第二条路径到(x,y))的时间已经大于之前的一条路径到的时间    t[x][y]=time;    for(int i=0;i<4;i++)    {        int nx=x+dxy[i][0],ny=y+dxy[i][1];        if(in(nx,ny)&&!vis[nx][ny]&&a[x][y])//当前点合法且不是陷进,且没有访问过,  //剪枝3:若访问过,也剪掉        {            vis[nx][ny]=1;//标记            dfs(nx,ny,time+a[nx][ny]);            vis[nx][ny]=0;//还原        }    }}int main(){    while(~scanf("%d",&n))    {        for(int i=0;i<n;i++)            for(int j=0;j<n;j++)            scanf("%d",&a[i][j]);        memset(vis,0,sizeof(vis));        memset(t,INF,sizeof(t));        ans=INF;///初始赋值        vis[0][0]=1;//起点的标记        dfs(0,0,a[0][0]);        printf("min=%d\n",ans);    }    return 0;}

这里写图片描述
因为DFS必须把所有可能到达终点的路径走一遍,再取最小值,所以用时比BFS慢

4.虽然之前说到BFS可以那么能否用双向BFS呢?
应该是可以吧。。还没试过,虽然是隐式图,从起点到终点,看似是有向图,但最优路径只有一条,那么也可以从终点到起点,双向BFS应该可以。。

原创粉丝点击