一,深度优先搜索:
事实上,深度优先搜索属于图算法的一种,英文缩写为DFS即Depth First Search.其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次。
其实这就像是一棵树的前序遍历。它从某个顶点v出发,访问此顶点,然后从v的未被访问的领接点
出发深度优先遍历图, 直至图中所有和v有路径相通的顶点都被访问到。若图中尚有顶点未被访问到,则另选图中一个曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。
伪代码
DFS(G)
//实现给定图的深度优先查找遍历
//输入:图G = <V,E>
//输出:图G的顶点,按照被DFS遍历第一次访问到的先后次序,用连续的整数标记
将V中的每个顶点标记为0,表示还“未访问”
count<——0
for 每个顶点 v in V do
if v 被标记为0
dfs(v)
dfs(v)
//递归访问所有和v相连接的未访问顶点,然后按照全局变量count的值
//根据遇到它们的先后顺序,给它们附上相应的数字
count<—— count + 1;用count标记v
for 每个顶点 w in V 邻接 to v do
if w 被标记为0
dfs(w)
模板:
void dfs()//参数用来表示状态
{
if(到达终点状态)
{
...//根据题意添加
return;
}
if(越界或者是不合法状态)
return;
if(特殊状态)//剪枝
return ;
for(扩展方式)
{
if(扩展方式所达到状态合法)
{
修改操作;//根据题意来添加
标记;
dfs();
(还原标记);
//是否还原标记根据题意
//如果加上(还原标记)就是 回溯法
}
}
}
举个例子:
迷宫
贴上我的垃圾代码:
#include<bits/stdc++.h>
using namespace std;
int a[6][6]={0},ans=0;
void dfs(int x,int y,int fx,int fy) {
if(x == fx && y == fy) {
ans++;
return;
}
else {
if(a[x][y] == 1)
a[x][y] = 0;
if(a[x+1][y] == 1)
dfs(x + 1,y,fx,fy),a[x+1][y]=1;
if(a[x-1][y] == 1)
dfs(x - 1,y,fx,fy),a[x-1][y]=1;
if(a[x][y+1] == 1)
dfs(x,y + 1,fx,fy),a[x][y+1]=1;
if(a[x][y-1] == 1)
dfs(x,y - 1,fx,fy),a[x][y-1]=1;
}
}
int main() {
int n,m,t,sx,sy,fx,fy,e,f;
cin>>n>>m>>t;
cin>>sx>>sy>>fx>>fy;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
a[i][j]=1;
for(int i=0;i<t;i++) {
cin>>e>>f;
a[e][f] = 0;
}
dfs(sx,sy,fx,fy);
cout<<ans;
return 0;
}
再看看题解大佬的代码做参考:
#include<iostream>//个人建议不使用万能头文件,如果要使用万能头文件,就不能定义数组map;
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
int map[6][6];//地图;
bool temp[6][6];//走过的标记;
int dx[4]={0,0,1,-1};//打表;
int dy[4]={-1,1,0,0};//打表;
int total,fx,fy,sx,sy,T,n,m,l,r;//total计数器,fx,fy是终点坐标,sx,sy是起点坐标,T是障碍总数,n,m是地图的长和宽,l,r是障碍的横坐标和纵坐标;
void walk(int x,int y)//定义walk;
{
if(x==fx&&y==fy)//fx表示结束x坐标,fy表示结束y坐标;
{
total++;//总数增加;
return;//返回,继续搜索;
}
else
{
for(int i=0;i<=3;i++)//0——3是左,右,下,上四个方向;
{
if(temp[x+dx[i]][y+dy[i]]==0&&map[x+dx[i]][y+dy[i]]==1)//判断没有走过和没有障碍;
{
temp[x][y]=1;//走过的地方打上标记;
walk(x+dx[i],y+dy[i]);
temp[x][y]=0;//还原状态;
}
}
}
}
int main()
{
cin>>n>>m>>T;//n,m长度宽度,T障碍个数
for(int ix=1;ix<=n;ix++)
for(int iy=1;iy<=m;iy++)
map[ix][iy]=1;//把地图刷成1;
cin>>sx>>sy;//起始x,y
cin>>fx>>fy;//结束x,y
for(int u=1;u<=T;u++)
{
cin>>l>>r;//l,r是障碍坐标;
map[l][r]=0;
}
walk(sx,sy);
cout<<total;//输出总数;
return 0;
}
二,广度优先搜索:
它按照一种同心圆的方式,首先访问所有和初始顶点邻接的顶点,然后是离它两条边的所有未访问顶点,以此类推,直到所有与初始顶点同在一个连通分量中的顶点都访问过了为止。
使用队列来跟踪广度优先搜索的操作是比较方便的,该队列先从遍历的初始顶点开始,将该顶点标记为已访问,再把它们入队。然后,将队头顶点从队列中移去。
伪代码:
BFS(G)
//实现给定图的广度优先查找遍历
//输入:图G = <V,E>
//输出:图G的顶点,按照被BFS遍历访问到的先后次序,用连续的整数标记
将V中的每个顶点标记为0,表示还“未访问”
count<——0
for 每个顶点 v in V do
if v 被标记为0
bfs(v)
bfs(v)
//递归访问所有和v相连接的未访问顶点,然后按照全局变量count的值
//根据遇到它们的先后顺序,给它们附上相应的数字
count<——count + 1;用count标记v并且用v初始化队列
while 队列不为空 do
for 每个邻接前顶点的顶点 w in V do
if w 被标记为0
count<—— count +1;用count标记w
把w放入队列
移出队列中的前顶点
对比图的DFS和BFS,你会发现,它们再时间复杂度上是一样的,不用之处在于对顶点访问的顺序不同。可见两者再全图遍历上是没有优劣之分的,只是视不同的情况选择不同的算法。
深度优先更适合目标比较明确,以找到目标为主要目的的情况,而广度优先更适合再不断扩大遍历范围时找到相对最优解的情况。
模板:
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int maxn=100;
bool vst[maxn][maxn]; // 访问标记
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(!vst[s.x][s.y] && ...) // 满足条件
return 1;
else // 约束条件冲突
return 0;
}
void bfs(State st)
{
queue <State> q; // BFS 队列
State now,next; // 定义2 个状态,当前和下一个
st.Step_Counter=0; // 计数器清零
q.push(st); // 入队
vst[st.x][st.y]=1; // 访问标记
while(!q.empty())
{
now=q.front(); // 取队首元素进行扩展
if(now==G) // 出现目标态,此时为Step_Counter 的最小值,可以退出即可
{
...... // 做相关处理
return;
}
for(int i=0;i<4;i++)
{
next.x=now.x+dir[i][0]; // 按照规则生成下一个状态
next.y=now.y+dir[i][1];
next.Step_Counter=now.Step_Counter+1; // 计数器加1
if(CheckState(next)) // 如果状态满足约束条件则入队
{
q.push(next);
vst[next.x][next.y]=1; //访问标记
}
}
q.pop(); // 队首元素出队
}
return;
}
int main()
{
......
return 0;
}
参考书籍:大话数据结构,算法设计与分析基础。