深度优先搜索DFS详解(百练2815 城堡问题)

我们先说说深搜到底是怎么工作的。
就是我们在解决一些问题的时候,会牵涉到各种各样的状态,然后各个状态之间可以互相转移,一个状态可以到达另外一个状态, 那往往我们就会把状态之间互相的可达性画成一个图, 那么对于深搜来讲的话,实际上就是去 遍历整个图的这么一个过程。

Dfs(v) {
if( v 访问过)
}
return; 将v标记为访问过;
对和v相邻的每个点u: Dfs(u);
int main() {
while(在图中能找到未访问过的点 k)
}
Dfs(k);

这里写图片描述
那怎么遍历呢?
首先图上面所有的点当然都是没有访问过的,在main里面可以用一个循环,在这循环里面每次找一个没有访问过的点k, 然后以这个点k作为起点,去调用dfs这个递归函数,做深度优先搜索。
然后在这个DFS里头的话,首先去看这个访问的这个点k,然后去判断一下这个点是不是被访问到了。如果访问到了,直接就返回了。如果没有被访问到就要首先标记一下它是访问过了,然后这时候就去找和这个v这个点相邻的每一个u点然后再去,再次去调用这个递归函数。所以相邻就是说从v可以一步就走到u,那我们对这种每一个相邻的点u我们都递归的调用dfs( u),从u开始再继续进行深度优先搜索。 如上图,这图它可不一定是连通的。对吧,那在做这个图的深度优先搜索的时候 假设一开始选了2这个点作为起点,也就是说 一开始选的这个点k是2,那我们从2出发,就可以走到4,然后从4出发又走到了8, 然后又走到了5,然后就走不动了,这块所有点都访问过了,而且到不了别的地方, 那这第一次以2为起点的深度优先搜索就结束了。然后就把跟2相连的点全部都照出来了。
所以如果是广度优先搜索的话,就应该是2,4,5,8
深度优先搜索它是2,4,8,5或者2,5,8,4都有可能吧
那现在以2为起点的深度优先搜索做完了整个图,还没有全部访问完,所以这个外循环,它还是能在图中找到没有被访问的点 比如说这个1,那就会从1开始做一遍深度优先搜索。 就可以走到1,3,6,7把剩下的部分走完。 这个就是深度优先搜索的一个基本的这个过程。一般来说是用递归来实现的。 我觉得递归会比较好的描述整个思路,比较直观,就按照正常的想法就是。

现在用道例题来说明下这个问题吧!

例题:百练2815 城堡问题
下图是一个城堡的地形图 。请你编写一个程序,计 算城堡一共有多少房间, 最大的房间有多大。城堡 被分割成m×n(m≤50, n≤50)个方块,每个方块可 以有0~4面墙。
t
输入
 程序从标准输入设备读入数据。
 第一行是两个整数,分别是南北向、东西向的方块数。
 在接下来的输入行里,每个方块用一个数字(0≤p≤50)描述。用一个数 字表示方块周围的墙,1表示西墙,2表示北墙,4表示东墙,8表示南 墙。每个方块用代表其周围墙的数字之和表示。城堡的内墙被计算两 次,方块(1,1)的南墙同时也是方块(2,1)的北墙。
 输入的数据保证城堡至少有两个房间。 
输出
 城堡的房间数、城堡中最大房间所包括的方块数。  结果显示在标准输出设备上。
样例输入
4
7
11 6 11 6 3 10 6
7 9 6 13 5 15 5
1 10 12 7 13 7 5
13 11 10 8 10 12 13 
样例输出
5 9

比如说这个方块它所对应的是11, 11这个数字它描述的是这个方块周围到底有哪些墙, 那墙不是有4个方向就是东南西北,那如果这个数字11里面包含一个1, 那它就说明这个方块它是有西墙的, 在这。如果这个数字里面还包含一个2, 那就说明这个房间它是有北墙的。如果这数字里面还包含一个4, 那它说明这个房间是有东墙的,如果还包含一个8,就说明它有西墙,那对于 这个方块来说,它所对应的数字是11,11可以分出来是1+2+8,所以, 它是没有东墙的,有其他的墙。等于说这个 一个数字,把它的二进制形式写出来,1011,这个东墙是0,就没有。

解题思路
 对每一个 方块,深度优先搜索,从而给这个方块能够到达的所有位置染色。最后统计一共用了几种颜色,以及每种颜色的数量。
 比如 1 1 2 2 3 3 3
1 1 1 2 3 4 3
1 1 1 5 3 5 3
1 5 5 5 5 5 3
 从而一共有5个房间,最大的房间(1)占据9 个格子

现在要找出最大的房间包含几个方块,以及一共有多少个房间。这个是典型的深度优先搜索的问题。 那基本的思路就是,可以从一个方块出发,看看这个从一个方块出发都能走到哪些其他的方块, 一直到走不动为止,那它走过的所有方块就可以形成一个房间。 那就把从一个方块出发所到达的其他所有的方块都给它 染上颜色,(其实就是标记一下,标记一下这是属于哪个房间的)。然后一个房间走完以后,就另外再找一个起点。 然后这个起点它必须不属于原来已经标记出来的这个房间,对吧,另外再找一个起点的方块, 再做一遍深度优先搜索,看看又能够到达哪些连通的方块,把这些连通的方块全部组成一个房间,再给它染色, 然后最后统计一下,染了几种颜色,以及每种颜色都有多少个 方块,通过这样的方式就可以直接去统计最后的这个 房间数和每个房间的方块数。

#include <iostream>
#include <stack>
#include <cstring>
using namespace std;

int R,C; //行列数
int rooms[60][60];//rooms当然就是用来存放对所有方块的这个描述
int color[60][60]; //房间是否染色过的标记
int maxRoomArea = 0;//maxRoomArea是这个最大房间的方块数
int roomNum = 0;//roomNum就是求得的一共有多少个房间
//roomArea是 正在探索的那一间房间它的面积,不断的探索这些房间它的面积就有可能不断增大
int roomArea;


void Dfs(int i,int k)//递归
{
    if( color[i][k] ) return;
    ++ roomArea;
    color [i][k] = roomNum;//roomNum就代表当前正在它所在的房间号
    if( (rooms[i][k] & 1) == 0 ) Dfs(i,k-1); //向西走
    if( (rooms[i][k] & 2) == 0 ) Dfs(i-1,k); //向北
    if( (rooms[i][k] & 4) == 0 ) Dfs(i,k+1); //向东
    if( (rooms[i][k] & 8) == 0 ) Dfs(i+1,k); //向南
}
/*
	cin >> n;
	if(n & 1) cout << "1" << endl;
    if(n & 2) cout << "2" << endl;
    if(n & 4) cout << "4" << endl;
    if(n & 8) cout << "8" << endl;
    可以用来判断二进制数位
*/



int main()
{
    cin >> R >> C;

    for( int i = 1;i <= R;++i)
    for ( int k = 1;k <= C; ++k)
        cin >> rooms[i][k];
    memset(color,0,sizeof(color));//所有的房间的颜色都初始化成0,都还没有标记过
    for( int i = 1;i <= R; ++i)
    for( int k = 1; k <= C; ++ k)
    {//每发现一个还没有走过的方块,它肯定属于某一个还没有被探索过的房间, 所以就要把这个房间的数目给它+1
        if( !color[i][k] )
        {
            ++ roomNum ;
            roomArea = 0;
            Dfs(i,k);
            maxRoomArea = max(roomArea,maxRoomArea);
            //用一个maxRoomArea就是已经找到的最大的房间面积去跟这个roomArea去比较, 然后有可能更改了这个maxRoomArea
        }
    }
    cout << roomNum << endl;
    cout << maxRoomArea << endl;
}

其实可以不使用递归,直接用栈来进行操作,代码如下:


#include <iostream>
#include <stack>
#include <cstring>
        using namespace std;
        
        int R,C; //行列数
        int rooms[60][60];//rooms当然就是用来存放对所有方块的这个描述
        int color[60][60]; //房间是否染色过的标记
        int maxRoomArea = 0;//maxRoomArea是这个最大房间的方块数
        int roomNum = 0;//roomNum就是求得的一共有多少个房间
            //roomArea是 正在探索的那一间房间它的面积,不断的探索这些房间它的面积就有可能不断增大
        int roomArea;

void Dfs(int r,int c)
{ //不用递归,用栈解决,程序其他部分不变
    struct Room
    {
        int r,c;
        Room(int rr,int cc):r(rr),c(cc) { }
    };
    stack<Room> stk;
    stk.push(Room(r,c));
    while ( !stk.empty() )
    {
        Room rm = stk.top();
        int i = rm.r;
        int k = rm.c;
        if( color[i][k])
            stk.pop();//已经被染色,就从把对应的这个栈顶的这个元素把它 pop出去
        else {
            ++ roomArea;
            color [i][k] = roomNum;
            if( (rooms[i][k] & 1) == 0 ) stk.push(Room(i,k-1)); //向西走
            if( (rooms[i][k] & 2) == 0 ) stk.push(Room(i-1,k)); //向北
            if( (rooms[i][k] & 4) == 0 ) stk.push(Room(i,k+1)); //向东
            if( (rooms[i][k] & 8) == 0 ) stk.push(Room(i+1,k)); //向南

        }
    }
}
        
    int main()
        {
            cin >> R >> C;
            
            for( int i = 1;i <= R;++i)
                for ( int k = 1;k <= C; ++k)
                    cin >> rooms[i][k];
            memset(color,0,sizeof(color));//所有的房间的颜色都初始化成0,都还没有标记过
            for( int i = 1;i <= R; ++i)
                for( int k = 1; k <= C; ++ k)
                {//每发现一个还没有走过的方块,它肯定属于某一个还没有被探索过的房间, 所以就要把这个房间的数目给它+1
                    if( !color[i][k] )
                    {
                        ++ roomNum ;
                        roomArea = 0;
                        Dfs(i,k);
                        maxRoomArea = max(roomArea,maxRoomArea);
                            //用一个maxRoomArea就是已经找到的最大的房间面积去跟这个roomArea去比较, 然后有可能更改了这个maxRoomArea
                    }
                }
            cout << roomNum << endl;
            cout << maxRoomArea << endl;
        }

猜你喜欢

转载自blog.csdn.net/william_munch/article/details/88614018