红与黑详解(洪水灌溉算法BFS与DFS实现,内有图解过程)



题目描述

有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。
你站在其中一块黑色的瓷砖上,只能向相邻(上下左右四个方向)的黑色瓷砖移动。
请写一个程序,计算你总共能够到达多少块黑色的瓷砖。

输入格式

输入包括多个数据集合。
每个数据集合的第一行是两个整数 WW 和 HH,分别表示 xx 方向和 yy 方向瓷砖的数量。
在接下来的 HH 行中,每行包括 WW 个字符。每个字符表示一块瓷砖的颜色,规则如下
1)‘.’:黑色的瓷砖;
2)‘#’:红色的瓷砖;
3)‘@’:黑色的瓷砖,并且你站在这块瓷砖上。该字符在每个数据集合中唯一出现一次。
当在一行中读入的是两个零时,表示输入结束。

输出格式

对每个数据集合,分别输出一行,显示你从初始位置出发能到达的瓷砖数(记数时包括初始位置的瓷砖)。

数据范围

1≤W,H≤20

输入样例

6 9 
....#. 
.....# 
...... 
...... 
...... 
...... 
...... 
#@...# 
.#..#. 
0 0

输出样例

45

解决问题

解题方法

洪水灌溉算法是适用于针对于网格图,方便处理连通块的算法,就是从一点开始搜索。算法复杂度为log(mn),与网格长宽有关。有bfs和dfs两种搜索顺序。bfs搜索为一层一层的搜索扩展,可以求最短距离,dfs搜索不用队列实现,代码更短。

解题思路

首先需要使用while循环来实现多组数据输入,然后循环内使用char数组存入题目给出的地图数据,边输入边寻找起点,找到起点后存储一个起点的下标。然后从起点进入搜索阶段。

BFS

灌溉使用BFS实现洪水灌,特点是利用队列。使用pair<int,int>来存储一个点的下标每组数据搜索之前要将之前搜过的标记清空,然后将起点标记为已经搜过,接着将起点设为队首。
当队列非空时,先取出队首,然后在上下左右搜索,搜索到未被搜过且在范围内的黑色瓷砖就进行标记,答案加一,同时接入队列。
最后输出答案即可。

流程伪代码

BFS伪代码
本图取自Acwing本题yxc的视频题解

注意点
  • 有关二维数组的困惑

举例:C/C++中我的理解
数组a[4]意思是a[列]
数组b[2][3]是一个两行三列的矩阵,意思为b[行][列]

一开始做题时由于对二维数组定义理解错误在

  • 存储x和y坐标时
  • 判断是否在范围内时

两部分代码实现时出现了x与y混淆。
以后一维数组x按列数理解二维数组按x为行数,y为列数理解(目前没想到更好的理解😖)

  • 有关取出队头与入队的时机的困惑
    • 取出队头是在每层扩展之前就开始
    • 入队必须是搜索到符合要求的陆地之后才开始

这样做的原因下面举例画图说明:

  • 标记数组可以让每个陆地只遍历一遍,入队出队一遍
详细模拟

BFS画图模拟红色不能走,黑色能走且未标记的点,绿色为当前队列中的点,黄色为已经出队并标记的点
BFS画图模拟·

实现代码
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
#define rep(i,m,n) for(int i=m;i<=n;i++)
#define per(i,m,n) for(int i=m;i>=n;i--)
#define PII pair<int,int>
#define endl "\n"
#define int long long
char f[25][25];
bool b[25][25];
int w, h;
int dx[4] = { -1,0,1,0 }, dy[4] = { 0,1,0,-1 };

int bfs(PII bg) {
    //从起点开始BFS
    int ans = 1;
    memset(b, false, sizeof(b));
    b[bg.first][bg.second] = true;
    queue<PII> q;
    q.push(bg);
    while (!q.empty()) {
        PII pt = q.front();
        q.pop();
        
        rep(i, 0, 3) {
            int nx = pt.first + dx[i], ny = pt.second + dy[i];
            if (nx <= h && nx >= 1 && ny <= w && ny >= 1 && !b[nx][ny] && f[nx][ny] == '.') {
                ans++;
                b[nx][ny] = true;
                q.push({nx,ny});
            }
        }
    }
    return ans;
}

signed main()
{
    ios::sync_with_stdio(false); cin.tie(0);
    while (cin >> w >> h && (w != 0 && h != 0)) {
        //循环输入
        PII bg;
        rep(i, 1, h) {
            rep(j, 1, w) {
                cin >> f[i][j];
                //初始起点
                //二维数组中[i]是行数,[j]是列数
                //如a[3]是长度为3的一行,只有列
                //a[4][5]则是4行5列
                if (f[i][j] == '@') 	bg.first = i,bg.second = j;	
            }
        }
        cout << bfs(bg) << endl;
    }
    return 0;
}

DFS

使用DFS实现洪水漫灌,无需使用pair<int,int>,利用函数的形参x,y即可。每组数据搜索之前要将之前搜过的标记清空,然后将起点标记为已经搜过,接着将起点设为队首。

流程伪代码

DFS伪代码
本图取自Acwing本题yxc的视频题解

注意点
  • 边搜索边处理,无需标记数组即可保证每个点只搜索一次
  • dfs函数在搜索递归过程中要将ans累加起来
详细模拟

dfs画图模拟红色不能走,黑色能走。ans为每次递归dfs函数的局部变量,最后输出的仅为dfs(起点)的那一个ans值
搜索顺序假定为上右下左
DFS画图模拟

实现代码
#include<iostream>
#include<cstring>
using namespace std;
#define rep(i,m,n) for(int i=m;i<=n;i++)
#define per(i,m,n) for(int i=m;i>=n;i--)
#define PII pair<int,int>
//#define x first
//#define y second
#define endl "\n"
#define int long long
char f[25][25];
int w, h;
int sx, sy;
int dx[4] = { -1,0,1,0 }, dy[4] = { 0,1,0,-1 };//上右下左

int dfs(int x, int y) {
	int ans = 1;
	//用涂色思想,搜索到就处理,保证只遍历一次,不需要标记数组
	f[x][y] = '#';
	rep(i, 0, 3) {
		int nx = x + dx[i], ny = y + dy[i];
		if (nx <= h && nx >= 1 && ny <= w && ny >= 1 && f[nx][ny] == '.') {
			//将邻近搜到的相加
			ans += dfs(nx, ny);
		}
	}

	return ans;
}

signed main()
{
	ios::sync_with_stdio(false); cin.tie(0);
	while (cin >> w >> h && (w != 0 && h != 0)) {
		//循环输入
		rep(i, 1, h) {
			rep(j, 1, w) {
				cin >> f[i][j];
				//第i行第j列
				//初始起点
				//sx为行数,sy为列数
				if (f[i][j] == '@') 	sx = i, sy = j;
			}
		}
		cout << dfs(sx, sy) << endl;
	}
	return 0;
}
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值