vlambda博客
学习文章列表

0基础学算法 搜索篇第一讲 深度优先搜索

0基础学算法 搜索篇第一讲 深度优先搜索


  相信绝大多数人对于深度优先搜索和广度优先搜索是不会特别陌生的,如果我这样说似乎你没听说过,那如果我说dfs和bfs呢?先不说是否学习过它们,至少它们的大名应该是都是听说过的吧,深度优先搜索(Depth-First-Search)和广度优先搜索(Breadth-First-Search)同为搜索(Search)中的两个大类。顾名思义,他们的作用便是在一定的元素当中选取符合要求的元素,有些情况下无论你使用dfs或bfs都能得出结果,但是事实上它们两个本质上是有区别的

第一讲深度优先搜索(Depth-First-Search)

  深度优先搜索是一种用途很广泛的算法,这种搜索方法使用的是一种比较基本的概念----递归,dfs就是基于递归而产生的一种严格意义上的算法,最开始的时候dfs是一种在开发爬虫早期使用较多的方法,它的目的是要达到被搜索结构的叶结点。那么深度优先搜索到底是如何实现的呢?

  要能理解dfs,首先理解递归是必要的(递归链接https://www.cnblogs.com/qj-Network-Box/p/12729230.html),在理解了递归后,就可以来看看这种算法的思想了

  假如你要搜寻节点①~节点⑩,然后你通过遍历找到了节点①,那么接下来你可以使用dfs找到接下来的节点

  

找到第一个节点是必须的,然后就可以接着往下递归


0基础学算法 搜索篇第一讲 深度优先搜索

然后就要一路找到底,即一直找到要回溯的节点


0基础学算法 搜索篇第一讲 深度优先搜索如果到了节点⑤的时候又找到了可以继续往下递归的条件,那么还可以继续往下搜索


0基础学算法 搜索篇第一讲 深度优先搜索然后在②处又找到了下面的一个节点

0基础学算法 搜索篇第一讲 深度优先搜索到了新的节点又可以向下搜搜

0基础学算法 搜索篇第一讲 深度优先搜索虽然现在回溯到了①,但是搜索还差几步,因为①再往下还有节点

0基础学算法 搜索篇第一讲 深度优先搜索要一直等到这里才是dfs最终结束的地方


 以上便是dfs的基本思想了,但是呢dfs并不是一种一成不变的算法,事实上不同的情况下所写出来的dfs是不同的,但是思路差不多就类似以上

深度优先搜索(Depth-First-Search)的例题

  在某个神奇的网站上,你总可以找到一些有趣的题巩固你学的知识,题目链接-->https://www.luogu.com.cn/problem/P1162

  这一道题算是一道比较基础的练习题吧(应该),其实这道题在你能够理解dfs的情况下,并找到思路的情况下应该是手到擒来的事。

  首先我们来仔细地读一读题大概意思就是被1框住的0要改写成2,我想很多人看到题的第一反应是想着怎么区别圈里面和圈外面的0,然后把圈里面的0改成2,似乎这个思路只有一个难点,那就是如何确定一个0到底处在圈里还是圈外,我开始也是这么想的,然后我想到了一个解决办法,如下图

0基础学算法 搜索篇第一讲 深度优先搜索一般的情况就如左图,所以我后来的分析都是基于左图的



0基础学算法 搜索篇第一讲 深度优先搜索然后我就想到了这个判定方法,也许你也想到了,但是我在实现这种方法的时候我发现了一个很大的瑕疵,假如那个圈不是规则的,而且不仅不规则,而且还是凹多边形这种思路就会出问题,问题如下图所示



如果测试点给的是这样图形,之前的判定方法就彻底失效了,因为很明显这个本来是一个在圈外的点,却会被判定成圈内的,那到底该如何识别一个0到底是圈外还是圈内呢?说实话,我到现在还是没想出来,但是很多时候我们就可以使用逆向思维大法,既然很难判定是否是圈内的,那就反过来判断他是不是圈外的不就完了?首先我们可以确定一个0处于方阵边缘处肯定不是圈内的,所以我们可以通过这个方法可以先找到边缘的零,那么,与它相邻的0以及相邻于与他相邻的0定然也是在圈外的,那么这道题dfs的思路也好说了,找到一个圈外边缘的0,标记一下,然后搜索与他相邻的0,并标记后继续搜索,由于题目要求,我们每到一点只要向周围四个方向搜索即可

供上搜索部分的代码

 1 int n,a[35][35]; 
2 int x1[4]={0,1,0,-1}; 3 int y1[4]={1,0,-1,0};//四个方向 4 void dfs(int x,int y) 5 { 6 a[x][y]=5; 7 for(int i=0;i<4;i++) 8    { 9 if(x+x1[i]>=0&&x+x1[i]<n&&y+y1[i]>=0&&y+y1[i]<n)10        {11 if(a[x+x1[i]][y+y1[i]]==0) dfs(x+x1[i],y+y1[i]);//dfs12        }13    }14 }

但是这道题只搜索这一次肯定是不够的,我们还需要考虑其他情况,比如下图

这种情况下有两块不在圈内的区域,所以我们需要不止一次地运行dfs,也就是说我们在主函数中可以将他的边缘“走”一圈以确保每一块圈外的0都可以被标记到



供上主函数+dfs

#include<iostream>using namespace std;int n,a[35][35]; 
int x1[4]={0,1,0,-1};int y1[4]={1,0,-1,0};void dfs(int x,int y)
{
   a[x][y]
=5;    for(int i=0;i<4;i++)
   {        
if(x+x1[i]>=0&&x+x1[i]<n&&y+y1[i]>=0&&y+y1[i]<n)
       {            
if(a[x+x1[i]][y+y1[i]]==0) dfs(x+x1[i],y+y1[i]);
       }
   }
}
int main(){
   cin
>>n;    for(int i=0;i<n;i++)    for(int j=0;j<n;j++)
   {
       cin
>>a[i][j];
   }    
for(int i=0;i<n;i++)    for(int j=0;j<n;j++)
   {        
if((i==0||i==n-1)&&a[i][j]==0) dfs(i,j);        else if(i>0&&(j==0||j==n-1)&&a[i][j]==0) dfs(i,j);
   }
   pp();    
return 0;
}

现在的代码就可以实现把所有不在圈里的0标记成5,在圈里的不变,不过代码还差最后一步,因为我们标记的时候将圈外的0标记成5,所以输出的时候要将所有是5的地方输出0,将原本是0的地方输出2,所以将这种输出处理一下就完成了

完整代码

#include<iostream>using namespace std;int n,a[35][35]; 
int x1[4]={0,1,0,-1};int y1[4]={1,0,-1,0};void pp()        
{    
for(int i=0;i<n;i++)
   {        
for(int j=0;j<n;j++)
       {            
if(a[i][j]==5) cout<<0;            else if(a[i][j]==1) cout<<1;            else if(a[i][j]==0) cout<<2;
           cout
<<" ";
       }
       cout
<<endl;
   }
}
void dfs(int x,int y)
{
   a[x][y]
=5;    for(int i=0;i<4;i++)
   {        
if(x+x1[i]>=0&&x+x1[i]<n&&y+y1[i]>=0&&y+y1[i]<n)
       {            
if(a[x+x1[i]][y+y1[i]]==0) dfs(x+x1[i],y+y1[i]);
       }
   }
}
int main(){
   cin
>>n;    for(int i=0;i<n;i++)    for(int j=0;j<n;j++)
   {
       cin
>>a[i][j];
   }    
for(int i=0;i<n;i++)    for(int j=0;j<n;j++)
   {        
if((i==0||i==n-1)&&a[i][j]==0) dfs(i,j);        else if(i>0&&(j==0||j==n-1)&&a[i][j]==0) dfs(i,j);
   }
   pp();    
return 0;
}

本期的内容就到这里了,如果你觉得对你有帮助,那么帮忙把赞👍点一下吧,如果还期待我日后的表现,就点个关注➕好了,我们下期说说bfs,再见!