一、DFS相关概念
深度优先搜索算法(Depth First Search):是一种用于搜索树或图的算法,深度优先,就是说每次都尝试向更深的节点走。
二、相关思想
深度优先搜索采用了回溯思想,沿着树的深度遍历树的节点,会尽可能深的搜索树的分支。当节点 v 的所在边都已被探寻过,搜索将回溯到发现节点 v 的前一节点,一直进行到已发现从源节点可达的所有节点为止。
在深度优先遍历的过程中,我们需要将当前遍历节点 v 的前节点存储起来,以便于在回溯时可以重新访问它们。遍历到的节点顺序符合「后进先出」的特点,有两种操作方法:递归法(已在前面的博文—回溯法中讲述)和 栈法
三、与BFS(广度优先搜索)的区别
BFS(广度优先搜索)采用队列的方式:先进先出,当遍历到某个节点时,尽可能地将该节点下一步可到达的节点全部入队,然后再出队,完成一次点的遍历。
DFS(深度优先搜索)采用栈的方式:后进先出,遍历某个节点时,尽可能深的遍历节点。
四、相关例题
例题1.迷宫问题
思路分析
输出从出口到入口的路径,此时需要考虑使用DFS遍历,将遍历过的节点赋为vis[x][y]=1
,当无法继续搜索时,回溯过程需将vis[x][y]=0,改变为原来的状态,最后当遍历节点到达终点时,经过的点即为路径(vis[x][y]=1的点)。
算法过程分析
1.由于前进方向:下-右-上-左 的顺序 ,当遍历到(0,6)时无法前进,此时回溯(0,5),由于(0,5)->(0,6)为前进方向,此时即以下一方向右,上,左考虑,依然无法前进,则同理回溯至(0,4)。(注:回溯过程需将vis[0][5] vis[0][6] 返回原来未经过的状态0)
2.依据刚才的遍历原则与前进方向原则,可以很好的遍历至终点(0,7),顺序如下
这里将以两种DFS的形式展现代码: 栈 与 递归
①栈的方法
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<iostream>
using namespace std;
typedef struct Sqstack{
int top;
int xx[1001],yy[1001]; // 取栈顶的 x y坐标
}Sqstack;
int d[4][2]={{1,0},{0,1},{-1,0},{0,-1}}; // 依照 下 右 上 左 的顺序
int n=8,x1,y1,x2,y2;
int a[101][101],an[1001][1001];
int vis[101][101]; // 判断是否经过
int flag;
void init(Sqstack &S)
{
S.top=-1;
return;
}
void push(Sqstack &S,int x,int y) // 栈的入队 相对来说好打
{
S.xx[++S.top]=x;
S.yy[S.top]=y;
}
int empty(Sqstack S)
{
if(S.top==-1) return 1;
else return 0;
}
int pop(Sqstack &s)
{
return s.xx[s.top--];
}
int gettop1(Sqstack S) {return S.xx[S.top];}
int gettop2(Sqstack S) {return S.yy[S.top];}
void dfs(Sqstack &s,int x,int y)
{
vis[x][y]=1;
push(s,x,y); // 入栈
while(!empty(s))
{
while(1)
{
int xn=gettop1(s);
int yn=gettop2(s); //取出
int i=++an[xn][yn]; // 注意:这里未避免回溯后又一次以向下的方法运行,这里记录每个点的运行次数
if(i==4) break; // 当四个方向都无法运行,则退出循环,出栈
if(xn==x2 && yn==y2) return ;//运行到终点时,直接返回
int x1=xn+d[i][0];
int y1=yn+d[i][1];
if(x1<0 || x1>=n || y1<0 || y1>=n) continue;// 边界
if(vis[x1][y1] || a[x1][y1]) continue; // 避免重新经过 与 障碍
vis[x1][y1]=1;
push(s,x1,y1);
}
vis[gettop1(s)][gettop2(s)]=0;
pop(s);
}
}
int main()
{
for(int i=0;i<=100;i++)
for(int j=0;j<=100;j++) an[i][j]=-1;
Sqstack s;
init(s);
// scanf("%d",&n);
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
scanf("%d",&a[i][j]);
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
dfs(s,x1,y1);
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
{
if(a[i][j]==1) printf("1 ");
else
{
if(vis[i][j]==1) printf("2 ");
else printf("0 ");
}
if(j==n-1) printf("\n");
}
return 0;
}
②递归方法
#include<stdio.h>
#include<math.h>
int n=8;
int a[101][101];
int x1,yx,x2,y2;
int flag;
int vis[101][101];
int b[4][2]={{1,0},{0,1},{-1,0},{0,-1}};
void dfs(int x,int y)
{
vis[x][y]=1;
if(x==x2 && y==y2) // 当到达终点时,返回,并做好标记,让后续回溯过程也可直接返回
{
flag=1;
return ;
}
for(int i=0;i<4;i++)
{
int x3=x+b[i][0];
int y3=y+b[i][1];
if(x3<0 || x3>=n || y3<0 || y3>=n) continue;
if(a[x3][y3]==1 || vis[x3][y3]==1) continue;
dfs(x3,y3);
if(flag==1) return;// 直接返回
vis[x3][y3]=0;
}
}
int main()
{
for(int i=0;i<n;i++)
for(int j=0;j<n;j++) scanf("%d",&a[i][j]);
scanf("%d%d%d%d",&x1,&yx,&x2,&y2);
dfs(x1,yx);
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
{
if(a[i][j]==1) printf("1 ");
else
{
if(vis[i][j]==1) printf("2 ");
else printf("0 ");
}
if(j==n-1) printf("\n");
}
return 0;
}
例题2.滑雪问题
关于样例的解释:当每下滑至某个节点时,区域长度+1
当从(2,2)节点出发时,经历的路程如图
栈与递归的方法都需要注意回溯过程
①栈的方法
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<iostream>
using namespace std;
typedef struct Sqstack{
int top;
int xx[1001],yy[1001]; // 取栈顶的 x y坐标
}Sqstack;
int maxn=0,step[101][101];
int d[4][2]={{1,0},{0,1},{-1,0},{0,-1}}; // 依照 下 右 上 左 的顺序
int n,m,x1,y1,x2,y2;
int a[101][101],an[1001][1001];
int vis[101][101]; // 判断是否经过
int flag;
int max1(int x,int y) { if(x>y) return x; else return y; }
void init(Sqstack &S)
{
S.top=-1;
return;
}
void push(Sqstack &S,int x,int y) // 栈的入队 相对来说好打
{
S.xx[++S.top]=x;
S.yy[S.top]=y;
}
int empty(Sqstack S)
{
if(S.top==-1) return 1;
else return 0;
}
int pop(Sqstack &s)
{
return s.xx[s.top--];
}
int gettop1(Sqstack S) {return S.xx[S.top];}
int gettop2(Sqstack S) {return S.yy[S.top];}
void dfs(Sqstack &s,int x,int y,int count,int x2,int y2)
{
vis[x][y]=1;
push(s,x,y); // 入栈
while(!empty(s))
{
while(1)
{
step[x2][y2]=max1(count,step[x2][y2]);
int xn=gettop1(s);
int yn=gettop2(s); //取出
int i=++an[xn][yn]; // 注意:这里未避免回溯后又一次以向下的方法运行,这里记录每个点的运行次数
if(i==4) break; // 当四个方向都无法运行,则退出循环,出栈
int x1=xn+d[i][0];
int y1=yn+d[i][1];
if(x1<0 || x1>=n || y1<0 || y1>=n) continue;// 边界
if(vis[x1][y1] || a[x1][y1]>=a[xn][yn]) continue; // 避免重新经过 与 障碍
vis[x1][y1]=1;
count++;
push(s,x1,y1);
}
vis[gettop1(s)][gettop2(s)]=0;
count--;
an[gettop1(s)][gettop2(s)]=-1; //回溯过程 全部状态改变的都应回溯
pop(s);
}
}
int main()
{
for(int i=0;i<=100;i++)
for(int j=0;j<=100;j++) an[i][j]=-1;
Sqstack s;
init(s);
scanf("%d",&n);
scanf("%d",&m);
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
scanf("%d",&a[i][j]);
for(int i=0;i<n;i++) // 从每个节点出发一次
for(int j=0;j<m;j++)
dfs(s,i,j,1,i,j);
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
if(step[i][j]>maxn)maxn=step[i][j];
printf("%d",maxn);
return 0;
}
②递归方法
#include<stdio.h>
#include<string.h>
int h[101][101];
int b[4][2]={{1,0},{0,1},{-1,0},{0,-1}};
int flag; int enter;
int step[1001][1001];
int vis[101][101];
int m,n;
int max1(int x,int y) { if(x>y) return x; else return y; }
void dfs(int x,int y,int count,int x2,int y2)
{
step[x2][y2]=max1(count,step[x2][y2]);
vis[x][y]=1;
for(int i=0;i<4;i++)
{
int x3=x+b[i][0];
int y3=y+b[i][1];
if(x3<0 || x3>=n || y3<0 || y3>=m) continue;
if(vis[x3][y3]==1) continue;
if(h[x3][y3]>=h[x][y])continue;
dfs(x3,y3,count+1,x2,y2); vis[x3][y3]=0;
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
for(int j=0;j<m;j++) scanf("%d",&h[i][j]);
int max=0;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
{
dfs(i,j,1,i,j);
vis[i][j]=0;
}
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
if(step[i][j]>max)max=step[i][j];
printf("%d",max);
return 0;
}