(bfs&dfs)洛谷P1451 求细胞数量
题目传送门:P1451 求细胞数量
题目描述
一矩形阵列由数字 0 到 9 组成,数字 1 到 9代表细胞,细胞的定义为沿细胞数字上下左右若还是细胞数字则为同一细胞,求给定矩形阵列的细胞个数。
输入格式
第一行两个整数代表矩阵大小 n 和 m。
接下来 n 行,每行一个长度为 m 的只含字符 0 到 9 的字符串,代表这个 n×m 的矩阵。
输出格式
一行一个整数代表细胞个数。
输入输出样例
输入 #1复制
4 10
0234500067
1034560500
2045600671
0000000089
输出 #1复制
4
说明/提示
数据规模与约定
对于 100% 的数据,保证 1≤n,m≤100。
分析:
这道题是一道非常简单的模板题,我做这道题是想更清晰的区别dfs和bfs。
从题意中可知细胞的定义为沿细胞数字上下左右若还是细胞数字则为同一细胞,即不论是上或下或左或右,只要不为0,则这一块是同一个细胞。简单的来说就是求连通块。
所以题目所给样例中有4个连通块。
这道题的输入有点意思,当我们把数组定义为int型时,我们不能直接输入每一个数,这时可以采用scanf("%1d",&arr[i][j]);来控制输入元素的个数。还有一种方法是我们把数组定义为char型,然后再输入。
接下来,先用上篇总结过的dfs来写这道题。
首先输入n和m,然后将每个元素输入。
然后我们需要判断边界条件以及合法状态(即是否被标记过)
接下来我们需要从四个方向(上下左右)搜索,看搜索到的元素是否满足边界条件或者处于合法状态。
如果不满足,则换下一个方向,否则标记一下。
每当一个连通块被搜索结束后,我们就记录一下块的个数,然后搜索下一个块。
dfs代码:
#include<bits/stdc++.h>
using namespace std;
int g[105][105];
int n,m,sum=0;
bool used[105][105];//标记数组
int dx[]={0,0,1,-1};//方向数组
int dy[]={1,-1,0,0};
//判断边界或是否被标记过
bool check(int a,int b)
{
if(a<1||a>n||b<1||b>m||g[a][b]==0||used[a][b]==false)
return false;
else
return true;
}
//深搜
void dfs(int a,int b)
{
used[a][b]=false;
for(int i=0;i<4;i++)
{
int tx,ty;
tx=a+dx[i];ty=b+dy[i];
if(!check(tx,ty))
continue;
dfs(tx,ty);
used[tx][ty]=false;
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
scanf("%1d",&g[i][j]);//有两种输入方法,当用char定义g[i][j]时,用cin>>g[i][j];用int定义g[i][j]时,用scanf("%1d",&g[i][j]);来控制输入的个数
used[i][j]=true;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(g[i][j]!=0&&used[i][j])
{
dfs(i,j);
sum++;
}
}
cout<<sum<<endl;
return 0;
}
接下来,我们采用bfs来做这道题。
首先,总结一下bfs算法。广度优先搜索(也称宽度优先搜索,缩写BFS,以下采用广度来描述)是连通图的一种遍历策略。因为它的思想是从一个顶点V0开始,辐射状地优先遍历其周围较广的区域。一般用来求最短路径, 迷宫问题。
它的遍历过程如下:首先访问指定的初始节点,然后若当前访问的节点的邻接节点有未被访问的,则依次访问每一个邻接节点;否则,返回到访问过的第一个邻接节点;然后看该节点是否有未被访问过的邻接节点,然后依次访问。如果没有邻接节点了,则返回到最开始访问的第二个邻接节点(初始节点访问的第二个),然后继续访问直到访问结束。然后再第三个,…,然后重复上述过程;最后遍历结束。
下面,我们用上篇的图来模拟一下:
首先我们随机选择一个节点作为起始点(以V1作为起始点为例)
以V1作为起始点(标记),与V1相邻的节点有V2,V3,依次访问V2,V3,访问V2(标记),访问v3(标记),然后V2有两个节点 V4,V5,依次访问V4(标记),v5(标记),V3有两个节点V6,V7,依次访问V6(标记),V7(标记);V4有一个节点 V8,访问V8(标记),遍历结束。
访问顺序:V1->V2->V3->V4->V5->V6->V7->V8
附上bfs模板:
int dir[4][2]= {0,1,0,-1,1,0,-1,0}; // 方向向量
struct State // BFS 队列中的状态数据结构
{
int x,y; // 坐标位置
int Step_Counter; // 搜索步数统计器
};
State a[maxn];
bool CheckState(State s) // 约束条件检验
{
if(!used[s.x][s.y] && ...) // 边界或未被标记
return true;
else // 约束条件冲突
return false;
}
void bfs(State st)
{
queue <State> q; // BFS 队列
State now,next; // 定义2 个状态,当前和下一个
q.push(st); // 入队
used[st.x][st.y]=false; // 访问标记
while(!q.empty())//当队列不为空时
{
now=q.front(); // 取队首元素进行扩展
if(now==G) //
{
...... // 做相关处理
return;
}
for(int i=0; i<4; i++)
{
next.x=now.x+dir[i][0]; // 按照规则生成下一个状态
next.y=now.y+dir[i][1];
if(CheckState(next)) // 如果状态满足约束条件则入队
{
q.push(next);//入队
used[next.x][next.y]=false; //访问标记
}
}
q.pop(); // 队首元素出队
}
}
接下来,我们套用模板来写这道题。
大部分步骤和dfs相同,唯一不同的地方是bfs用的是队列,并且不用回溯。注意哦,队列是先进先出。
bfs代码:
#include<bits/stdc++.h>
using namespace std;
int g[105][105];
struct node{
int x,y;
};
int n,m,sum=0;
bool used[105][105];//标记数组
int dx[]={0,0,1,-1};//方向数组
int dy[]={1,-1,0,0};
//判断边界或是否被标记过
bool check(int a,int b)
{
if(a<1||a>n||b<1||b>m||g[a][b]==0||used[a][b]==false)
return false;
else
return true;
}
//广搜
void bfs(int a,int b)
{
queue<node> q;
node now,next;
used[a][b]=false;
now.x=a;now.y=b;
q.push(now);//入队
while(!q.empty())
{
now=q.front();//返回队首元素
for(int i=0;i<4;i++)
{
int tx=now.x+dx[i];
int ty=now.y+dy[i];
if(!check(tx,ty))
continue;
next.x=tx;next.y=ty;
used[tx][ty]=false;
q.push(next);
}
q.pop();//首元素出队
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
scanf("%1d",&g[i][j]);//有两种输入方法,当用char定义g[i][j]时,用cin>>g[i][j];用int定义g[i][j]时,用scanf("%1d",&g[i][j]);来控制输入的个数
used[i][j]=true;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(g[i][j]!=0&&used[i][j])
{
bfs(i,j);
sum++;
}
}
cout<<sum<<endl;
return 0;
}