问题总结:
一、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++;
}
}
二、在输入和遍历时,下标的边界范围出问题。
-
下标起点不统一;
-
等号打还是不打;
-
列循环为方便,直接复制行循环不修改终点,导致行列都是"n";
-
行列下标起点定为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递归作用的题
自己总结的DFS模板:
//DFS模板
void dfs(pos)//参数列表:表示递归位置、层数
{
if()//递归的结束条件:递归的底,根据递推出的每一个结果做出操作
{
visit();
return ;
}
else
{
//访问根节点并做好标记
...
//访问邻接点
for(;;next_pos=fun()){
dfs(next_pos);
//恢复现场(有必要的话)
...
}
}
return ;
}