DFS——求连通子图数


5565. 残垣断壁 - AcWing题库

问题总结:

一、DFS思路不够清晰

1.DFS子函数比较模糊,理解不够深刻直接。

两步:

访问根节点、进而访问全部邻接点。访问完后回到上一层,上一层做另一个选择。

下面是DFS子函数模板:
//模板一
void dfs(int x,int y,int u){
	//标记根节点(这里只是检索,不需要额外的访问操作,也可以把标记看做对坐标的访问操作)
	visited[x][y]=true;
	//标记所有邻接点
	for(int i=0;i<4;i++)
	{
		int tx=x+dx[i];
		int ty=y+dy[i];
		if(tx>n||tx<=0||ty>m||ty<=0||visited[tx][ty]==true||b[tx][ty]=='.')
			continue;
		dfs(tx,ty,u+1);
	}
    //搜索到边缘(记录路径)
    //这里四个分支都触底,即是“递归的底”
	printf("这是第%d层\n",u);
}
//模板二
bool check(int x, int y){
    if(x>0 && y>0 && x<=n && y<=m && mp[x][y]=='B') return 1;
    else return 0;
}

void dfs(int x, int y, int u){
    //访问根节点(包含标记)
    mp[x][y]='.';
    //访问所有邻接点(包含标记)
    if(check(x-1,y)) dfs(x-1,y,u+1);
    if(check(x+1,y)) dfs(x+1,y,u+1);
    if(check(x,y-1)) dfs(x,y-1,u+1);
    if(check(x,y+1)) dfs(x,y+1,u+1);
    //搜索到边缘(记录路径)
    //这里四个分支都触底,即是“递归的底”
 	printf("这是第%d层\n",u);   
}

2.DFS外函数选择搜索起始点时,判断条件出问题。

if(!visited[i][j]){//只需要从“没有访问过”的“B”开始
	dfs(i,j,b,1);
	cnt++;//空缺的visited都是false,但是连通图不需要从它开始,会有不必要的cnt+++
}
if(b[i][j]=='B'){//这里会重复访问已访问过的B,导致重复遍历一个连通图,cnt做了重复的++
	dfs(i,j,b,1);
	cnt++;
}

下面是DFS外函数模板:
for(int i=n;i>=1;i--)//(1~n)
	for(int j=1;j<=m;j++){//(1~m)
		if(b[i][j]=='B'&&!visited[i][j]){
			dfs(i,j,1);
			cnt++;
		}
	}
for(int i=n-1;i>=0;i--)//(0~n-1)
	for(int j=0;j<m;j++){//(0~m-1)
		if(b[i][j]=='B'&&!visited[i][j]){
			dfs(i,j,b,1);
			cnt++;
		}
	}

二、在输入和遍历时,下标的边界范围出问题。

  1. 下标起点不统一;

  2. 等号打还是不打;

  3. 列循环为方便,直接复制行循环不修改终点,导致行列都是"n";

  4. 行列下标起点定为1,但是使用了cin>>b[i];的形式进行“行输入”,但是这是下标起点为0的输入。

本题AC代码:

字符数组版本

#include<iostream>
#include<vector>
using namespace std;

int n=0,m=0,cnt=0;
char b[110][110];
bool visited[101][101]={0};
//右、上、左、下
int dx[]={0,-1,0,1};//行
int dy[]={1,0,-1,0};//列


void dfs(int x,int y,int u){
	//访问根节点
	visited[x][y]=true;
	//访问邻接点
	for(int i=0;i<4;i++)
	{
		int tx=x+dx[i];
		int ty=y+dy[i];
		if(tx>n||tx<=0||ty>m||ty<=0||visited[tx][ty]==true||b[tx][ty]=='.')
			continue;
		dfs(tx,ty,u+1);
	}
	printf("这是第%d层\n",u);
}

int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			cin>>b[i][j];
	//无论是输入时,还是遍历时,下标的起始位置都要统一,要么都从0开始,要么都从1开始
	for(int i=n;i>=1;i--)
		for(int j=1;j<=m;j++){
			if(b[i][j]=='B'&&!visited[i][j]){
				dfs(i,j,1);
				cnt++;
			}
		}
	cout<<cnt;
	return 0;
}

vector版本

#include<iostream>
#include<vector>
using namespace std;

int n=0,m=0,cnt=0;

bool visited[100][100]={0};
//原地、右、上、左、下
int dx[]={0,-1,0,1};//行
int dy[]={1,0,-1,0};//列


void dfs(int x,int y,vector<vector<char>> b,int u){
	//访问根节点
	visited[x][y]=true;
	//访问邻接点
	for(int i=0;i<4;i++)
	{
		int tx=x+dx[i];
		int ty=y+dy[i];
		if(tx>=n||tx<0||ty>=m||ty<0||visited[tx][ty]==true||b[tx][ty]=='.')
			continue;
		dfs(tx,ty,b,u+1);
	}
	printf("这是第%d层\n",u);
}

int main(){
	cin>>n>>m;
	vector<vector<char>> b(n,vector<char>(m,'.'));
	for(int i=0;i<n;i++)
		for(int j=0;j<m;j++)
			cin>>b[i][j];
	
	for(int i=n-1;i>=0;i--)
		for(int j=0;j<m;j++){
			if(b[i][j]=='B'&&!visited[i][j]){//只需要从没有访问过的b开始
				dfs(i,j,b,1);
				cnt++;//空缺不需要,也不需要对应cnt+++
			}
		}
	cout<<cnt;
	return 0;
}

回顾:

学习的第一个DFS题

P1596 [USACO10OCT] Lake Counting S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

#include<stdio.h>
//#define N 101
const int N=101;//图的最大容量101*101;

int row, col;//顶点的位置
char graph[N][N]; //= { 0 };//顶点集
int delt[8][2] = { {0,1},{1,1},{1,0},{1,-1},
{0,-1},{-1,-1},{-1,0},{-1,1} };//向量集(行,列)
int flag[N][N];//= { 0 };//访问标志
int lakes = 0;//计数器

void dfs(int y, int x) {//坐标格式为(行,列)=(y,x)
	for (int i = 0; i < 8; i++) {
		int y_next = y + delt[i][0];//行偏移		
		int x_next = x + delt[i][1];//列偏移
		if (x_next >= 0 && y_next >= 0 && x_next < col && y_next < row)
		{	//下一邻接点
			if (graph[y_next][x_next] == 'W' && flag[y_next][x_next] == 0)//找有水的邻接点
			{
				flag[y_next][x_next] = 1;//标记此水格已搜过
				dfs(y_next, x_next);//从下一个水格继续搜索,深度优先
				//直至某个点周围都没有水或有水的已搜过
				//即一直搜到树的端点,水坑的边缘,即找完这个水坑的全部
				//就回到根部,完成递归,结束本次dfs函数,得到一个水坑
			}
		}	
	}
	//return;
}

int main() {
	scanf("%d%d", &row, &col);
	int i, j;
	for (i = 0; i < row; i++) {
		scanf("%s", graph[i]);
	}
	for (i = 0; i < row; i++) {
		for (j = 0; j < col; j++)
			if (graph[i][j] == 'W' && flag[i][j] == 0) {
				dfs(i, j);
				lakes++;
			}
	}
	printf("%d", lakes);
	return 0;
}

这里dfs子函数有点问题,没有访问起始点,但是也过了,不知道是不是自己的理解有问题:

实际上是没有的,只是不方便理解;
第一次搜索连通图的时候最后还是会搜到起始点,只要没被访问的连通结点都会在一次DFS中全部被遍历,所以起始点就算第一次没有访问,也会在后面被访问。

向量集如果加入{0,0},会导致每次访问邻接点的时候做一次不必要的判断,所以只需要描述指向其他点的向量就好。

开始理解DFS递归作用的题

蓝桥杯算法学习纪实——递归实现排列型枚举-CSDN博客

自己总结的DFS模板:

//DFS模板
void dfs(pos)//参数列表:表示递归位置、层数
{
	if()//递归的结束条件:递归的底,根据递推出的每一个结果做出操作
	{
		visit();
		return ;
	}
	else
	{
		//访问根节点并做好标记
		...
		//访问邻接点
		for(;;next_pos=fun()){
			dfs(next_pos);
			//恢复现场(有必要的话)
			...
		}
	}
	return ;
}

紫皮书模板:(待看书) 

  • 10
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 要获得网络的最大连通子图,你可以使用称为 "Kosaraju 算法" 的算法。它的基本思想是: 1. 使用深度优先搜索算法出图中的所有强连通分量。 2. 将图的所有边反向,再出所有的强连通分量。 3. 最大的强连通分量就是原图的最大连通子图。 下面是具体的步骤: 1. 使用深度优先搜索算法出图中的所有强连通分量。 2. 将图的所有边反向,再出所有的强连通分量。 3. 最大的强连通分量就是原图的最大连通子图。 ### 回答2: 要获得网络的最大连通子图,首先需要了解网络的拓扑结构以及节点之间的连接关系。以下是一种方法,以获得无向图的最大连通子图为例: 首先,我们可以使用深度优先搜索算法DFS)或广度优先搜索算法(BFS)来遍历整个图。从某个节点开始,通过搜索与该节点直接或间接相连的所有节点,并将它们标记为已访问。再选择另一个未访问的节点作为起始点,重复此过程,直到所有节点都被访问。 在搜索的过程中,我们记录每个连通子图的节点,并使用一个变量来保存当前最大的连通子图节点以及对应的子图。在搜索完成后,最大连通子图就是记录的节点最多的连通子图。 如果需要获得有向图的最大连通子图,可以先将有向图转换为无向图。具体做法是遍历有向图中的所有边,将每条边的起始节点和结束节点之间增加一条双向边。之后再按照上述方法寻找无向图的最大连通子图。 除了上述方法,还有其他算法可以用于获得网络的最大连通子图,如最小生成树算法、Kruskal算法和Prim算法等。根据实际情况和需选择适合的算法。 总之,获得网络的最大连通子图可以通过搜索算法遍历网络节点,并记录每个连通子图的节点来获得。具体选择哪种算法取决于网络的性质和需。 ### 回答3: 获得网络的最大连通子图的方法有一下几种: 1. BFS(广度优先搜索)算法:从一个节点开始,利用广度优先搜索的方式逐层遍历网络中的节点,并将遍历到的节点标记为已访问。通过这种方式,可以获得从起始节点出发可到达的所有节点,即最大连通子图。 2. DFS深度优先搜索)算法:从一个节点开始,利用深度优先搜索的方式递归地遍历网络中的节点,并将遍历到的节点标记为已访问。通过这种方式,同样可以获得从起始节点出发可到达的所有节点,即最大连通子图。 3. 并查集算法:首先初始化每个节点为一个独立的集合,然后依次遍历网络中的边,将边连接的两个节点合并成为一个集合,直到遍历完所有边。最终,每个集合中的节点即为最大连通子图中的节点。 4. 最小生成树算法:使用最小生成树算法(如Prim算法或Kruskal算法)可以获得连接网络中所有节点的最小权重的边集合,这些边所连接的节点即为最大连通子图中的节点。 需要注意的是,网络中可能存在多个最大连通子图,以上方法得到的是其中之一。如果需要找到所有最大连通子图,可以通过对网络中的所有节点逐个应用以上方法,并使用递归或迭代的方式来获取所有的最大连通子图

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值