不撞南墙不回头----深度优先搜索

本文介绍了深度优先搜索(DFS)算法,它是图算法的一种,通过不断深入分支路径并回溯来遍历图。文中指出当地图范围大时使用该算法易超时,但对蓝桥杯填空题有帮助。还给出两个实例,“Lake Counting”和“马走日”,分别展示了深搜在块连通问题和棋盘遍历问题中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言:

       深度优先搜索属于图算法的一种,英文缩写为DFS即Depth First Search。其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次。(即不撞南墙不回头)。

       举一个例子:

          对于这个图,从节点A进行深度优先搜索,(假设默认搜索都是先搜索左孩子,后搜索右孩子),那么搜索的顺序就是A-->B-->D-->E-->F-->G-->C;下面我来具体的模拟一下过程:第一个搜索的位置是A,A有两个孩子分别是B,C,但是我们默认搜索顺序是先左孩子后右孩子,所以下一个搜索的节点就是B,其中B也是有两个孩子,所以按照顺序我们要搜索左孩子D,对于D我们发现D没有孩子节点,这也就是我们说的撞到了南墙,那么接下来就要回头了,退回到节点B,节点B有两个孩子,但是节点D我们已经搜索过了,所以现在我们就要搜索节点E,到了节点E,我们发现节点E有两个子节点,我们下一步就要搜索节点F,F节点没有孩子节点(撞了南墙,所以现在要回头),退回到节点E,发现节点G没有走过,那么下一步就是走节点G,既然点G没有子节点,我们就要退回节点E,然后我们发现节点E的所有节点我们都走过,那么我们接着退回节点B,发现节点B的所有节点我们也是全部走过,那么我们接着退回节点A,发现节点C没有搜索过,那么我们就搜索节点C,节点C没有子节点,我们接着退,退回到节点A,我们发现节点A的所有节点我们都搜索过,那么这次以节点A开始的深度优先搜索就结束了。

        其实我们可以看出来,深度优先搜索算法就是在遍历整个图,把每一个节点都会走一遍,不停的尝试。其中在尝试的过程中有很多的退回,这个就是深度优先搜索中的回溯。看看上面我对于深度优先搜索过程的描述你就可以发现,整个算法在不停的搜索回溯,过程很麻烦,所以当你在比赛时就应该注意了,当地图的范围很大时就不能够使用深度优先算法,很容易超时。但是学好深度优先算法对于蓝桥杯还是很有优势的,填空题很多都要用到深搜。

       知道了深搜的方法,现在的问题就是如何用代码来实现。其实不难发现,深搜就是一个不停的递归,回溯的过程,我们只需要用一个数组来标记这个点是否被搜索过,然后不停的递归即可。深搜就是把所有的可能都走一遍,尝试所有的结果,这样可以帮助我们解决很多的问题。深搜我们大多数都是对于图的搜索,所以当我们碰到了问题,就是先要建图,然后再搜索。

       下面找一个关于深搜的简单的例子吧。

 

Lake Counting

POJ - 2386 就是一道简单的深搜问题(块连通问题)。

题目如下:

Due to recent rains, water has pooled in various places in Farmer John's field, which is represented by a rectangle of N x M (1 <= N <= 100; 1 <= M <= 100) squares. Each square contains either water ('W') or dry land ('.'). Farmer John would like to figure out how many ponds have formed in his field. A pond is a connected set of squares with water in them, where a square is considered adjacent to all eight of its neighbors.

Given a diagram of Farmer John's field, determine how many ponds he has.

Input

* Line 1: Two space-separated integers: N and M

* Lines 2..N+1: M characters per line representing one row of Farmer John's field. Each character is either 'W' or '.'. The characters do not have spaces between them.

Output

* Line 1: The number of ponds in Farmer John's field.

Sample Input

10 12
W........WW.
.WWW.....WWW
....WW...WW.
.........WW.
.........W..
..W......W..
.W.W.....WW.
W.W.W.....W.
.W.W......W.
..W.......W.

Sample Output

3

Hint

OUTPUT DETAILS:

There are three ponds: one in the upper left, one in the lower left,and one along the right side.

题意:

        有一个N*M的地图,其中“W”代表水,“.”代表干的地方,让你求出来整个地图中有多少的水坑。我们可以明显的看出来有三个水坑,接下来就是用代码来进行实现啦。这道题的连通性是指八个方位的连通。

思路:

       这道题我们就可以用刚刚学会的深度优先搜索算法啦,在开始的地图中,找到“W”就开始搜索,在搜索的过程中把“W”改成别的字符,代表着搜索过了,所有相连的都改变,那么我们可以发现,最后我们搜索的次数就是水坑的个数。最后输出结果就可以了。这个就是深搜的入门题,当然,我简化了过程,并没有用到数组的标记,但是把“W”改成别的字符也就代替了数组的标记,因为它也可以起到标记是否搜索过的作用。

代码如下:

#include<stdio.h>
#include<string.h>

int n,m,s;
char map[101][101];//存储地图;
int next[8][2]= {{1,0},{-1,0},{0,1},{0,-1},{-1,1},{-1,-1},{1,1},{1,-1}};//连通的八个方位;

void dfs(int x,int y)//搜索;
{
    map[x][y]='.';//把这一点的“W”改成“.”,代表搜索过;
    for(int i=0; i<8; i++)
    {
        int nx=x+next[i][0];//周围的坐标的位置;
        int ny=y+next[i][1];
        if(map[nx][ny]=='.')//这个点搜索过,或者这个点为干地,不能搜索,就结束;
            continue;
        if(nx<0||nx>=n||ny<0||ny>=m)//下一个坐标超出了地图的范围;
            continue;
        dfs(nx,ny);//这个点没有被搜索过,且这个点的范围没有超出地图的范围,所以就可以接着搜索下一步;
    }
    return ;
}


int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        for(int i=0; i<n; i++)//输入地图;
            scanf("%s",map[i]);
        s=0;
        for(int i=0; i<n; i++)//开始搜索;
        {
            for(int j=0; j<m; j++)
            {
                if(map[i][j]=='W')//有水就开始搜索;
                {
                    s++;//水坑的数量增加;
                    dfs(i,j);//搜索;
                }
            }
        }
        printf("%d\n",s);
    }
    return 0;
}

上面的这道题,省略了数组标记的过程,那么就再来一道需要用数组标记的深搜来练练手吧。

马走日

OpenJ_Bailian - 4123

题目如下:

马在中国象棋以日字形规则移动。

请编写一段程序,给定n*m大小的棋盘,以及马的初始位置(x,y),要求不能重复经过棋盘上的同一个点,计算马可以有多少途径遍历棋盘上的所有点。

Input

第一行为整数T(T < 10),表示测试数据组数。
每一组测试数据包含一行,为四个整数,分别为棋盘的大小以及初始位置坐标n,m,x,y。(0<=x<=n-1,0<=y<=m-1, m < 10, n < 10)

Output

每组测试数据包含一行,为一个整数,表示马能遍历棋盘的途径总数,0为无法遍历一次。

Sample Input

1
5 4 0 0

Sample Output

32

思路:

        这道题就是明显的深度优先搜索的标准问题了(走完所有的点并且不重复,计算所有的方法),那么我们就可以用递归来不停的尝试,尝试所有的结果。

代码如下:

#include<stdio.h>
#include<string.h>

int map[10][10];//地图;
int t,n,m,x,y;
int sum=0;//结果;
int nextt[8][2]=
{
    {1,2},{1,-2},{-1,2},{-1,-2},{2,1},{2,-1},{-2,1},{-2,-1}
};//八个马能够走的位置,需要注意的是,中国象棋中的马是走“日”的;

void dfs(int a,int b,int s)//横坐标,纵坐标,已经搜索过的点的个数;
{
    if(s==n*m)//所有的点都被搜索过了;
        sum++;
    if(map[a][b]==0)//这个点为0,戴白哦这这个点没有被搜索过,即马没有走过;
    {
        map[a][b]=1;//现在把这个点标记为马走过了;
        for(int i=0; i<8; i++)//搜索从这个点出发,马可以走的其别的位置;
        {
            int nx=a+nextt[i][0];
            int ny=b+nextt[i][1];
            if(nx<0||nx>=n||ny<0||ny>=m)//超出地图的范围了,不可以;
                continue;
            if(map[nx][ny]==1)//这个点走过了,不可以;
                continue;
            dfs(nx,ny,s+1);//符合条件的就搜索,记得搜过的点的个数要加一;
        }
        map[a][b]=0;//超级重要,记得把刚刚改的标记再改回来,
        //因为要尝试所有的结果,这个点不改回来会对下一次的尝试有影响;
    }
    return ;
}

int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d%d%d",&n,&m,&x,&y);
        memset(map,0,sizeof map);//清0,代表这个点没有走过;
        sum=0;
        dfs(x,y,1);
        printf("%d\n",sum);
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值