DFS(深度优先算法)
1. 原理概述
最基本的DFS是用来解决无向图的遍历问题的。无向图用沿主对角线对称的邻接矩阵存储。DFS算法最基本的代码实现如下:
void dfs(int cur)//cur是当前所在的顶点编号
{
sum++;//每访问一个顶点,sum+1
if(sum==n) return;
for(i=1;i<=n;i++)//从1号顶点到n号顶点依次尝试,看哪些顶点与当前顶点cur有边相连
{
if(e[cur][i]==1 && book[i]==0)
{
book[i]=1;//标记顶点i已经访问过
dfs(i);//从顶点i再出发继续遍历
}
}
}
上面,cur存储当前正在遍历的顶点,二维数组e存储的是图的边(邻接矩阵),book用来记录哪些顶点已经访问过,sum用来记录已经访问过的顶点个数,n存储图中顶点的总个数。
2. 用DFS求所有可能的排列组合
以上是最基本的dfs算法实现,事实上,代码结构绝不是一成不变的。
例如,应用dfs求一串元素的所有可能的排列,抽象出来的图的分支就十分庞大。我们不关心对整张图的全部遍历,而是希望分别输出每种可能的完整排列顺序。这就需要将dfs的步骤改成:
- 定义全局的用于存放所有解的对象和存放一次解的对象。
- 在递归函数中,确定递归终止条件(一般为sum==n),并在终止时将一次完整的解存档。
在递归函数中,若没有终止,代码结构应该是:则先执行动作,调用递归,再撤销动作。
一个完整的实现代码为:
void dfs(int s, vector<int> &nums){
if(s == nums.size()) result.push_back(oneResult);
for(int i = 0; i < nums.size(); i++){
if(used.count(nums[i]) != 0) continue;//如果元素已经存在,就pass
//执行动作
used.insert(nums[i]);
oneResult.push_back(nums[i]);
dfs(s + 1, nums);
//撤销动作
oneResult.pop_back();
used.erase(nums[i]);
}
}
其中,名为used的set对象用于存储已经使用过的元素,类似book数组。result存放所有可能的结果,而oneResult存放一次完整的排列结果。
3. 用DFS求城市最短路程问题
首先用一个二维矩阵表示任意两个城市之间的路程。
核心代码如下:
void dfs(int cur,int dis)
{
int j;
//如果当前走过的路程已经大于之前找到的最短路,就没有必要往下尝试了
if(dis>min) return;
if(cur==n)//如果到达了目标城市
{
if(dis<min) min=dis;//更新最小值
return;
}
for(j=1;j<=n;j++)
{
//判断当前城市cur到城市j是否有路,并判断城市j是否已经走过
if(e[cur][j]!=INT_MAX && book[j]==0)
{
book[j]=1;//标记城市j已经走过
dfs(j,dis+e[cur][j]);
book[j]=0;//撤销对城市j的标记
}
}
}
4 先序遍历和DFS
对于二叉树而言,先序遍历和深度优先遍历的效果是一样的。
BFS(广度优先遍历)
1. 原理概述
如果需要分层遍历图,就需要用到deque数据结构(可以用数组加两个辅助指针模拟队列)。BFS的主要思想是:首先以一个未被访问过的顶点作为起始顶点,访问其所有相邻的顶点,然后对每个相邻的顶点,再访问它们相邻的未被访问过的顶点,直到所有顶点都被访问过,遍历结束。
代码实现为:
int book[1001],que[1001],head,tail;//head和end模拟队列的队首和队尾位置
head=1;
tail=1;
//从1号顶点出发,将1号顶点加入队列
que[tail]=1;
tail++;
book[1]=1;//标记1号顶点已访问
//BFS核心部分
while(head<tail)//当队列不为空的时候循环
{
cur=que[head];
for(i=1;i<=n;i++)
{
//判断从顶点cur到顶点i是否有边,并判断顶点i是否已经被访问过
if(e[cur][i]!=INT_MAX && book[i]==0)
{
//将顶点i加入队列
que[tail]=i;
tail++;
book[i]=1;
}
//如果tail大于n,说明所有顶点都被访问过
if(tail>n)
{
break;
}
}
//当一个顶点拓展结束后,要将head++,相当于从队首删除该顶点
head++;
}
2. 用BFS解最少转机问题
假设有当前城市和目标城市,两者没有直航,但有和其他城市的航班往来。希望找到一种最少转机的方法。
这个问题中,我们不关心两个城市间的往来时间(边的权值),如果可以往来,一律视为有权值为1的边。我们关心从城市A到B经过的边的数量最少。这也是DFS和BFS的不同应用之处。
核心代码如下:
while(head<tail)
{
cur=que[head].x;//que是一个结构体数组,每个元素是一个结构体,x表示城市编号,s表示转机次数
for(j=1;j<=n;j++)
{
if(e[cur][j]!=INT_MAX && book[j]==0)//从cur到j是否有航班,并且判断j是否已经经过
{
que[tail].x=j;
que[tail].s=que[head].s+1;//转机次数+1
tail++;
book[j]=1;
}
//如果已经到达目标城市
if(que[tail].x==end)
{
flag=1;
break;
}
}
//当一个点拓展完后,head++
head++;
}
DFS和BFS算法的区别
从前面分别举的几个例子可以看出,DFS和BFS应用场景并不相同。
当我们关心一条路径的权重合时,适合用DFS算法。
当我们关心一条路径的边的数量时,适合用BFS算法。