新的方法和概念,常常比解决问题本身更重要。——华罗庚
目录
1 引子
图是一种灵活的数据结构,一般作为一种模型用来定义对象之间的关系或联系。对象由顶点(V)表示,而对象之间的关系或者关联则通过图的边(E)来表示。
图可以分为有向图和无向图,一般用G=(V,E)来表示图。经常用邻接矩阵或者邻接表来描述一副图。
在图的基本算法中,最初需要接触的就是图的遍历算法,根据访问节点的顺序,可分为广度优先搜索(BFS)和深度优先搜索(DFS)。
2 深度优先搜索(DFS)
深度优先搜索(Depth First Search,DFS):一种用于遍历或搜索树或图的算法。沿着树的深度遍历图的节点,尽可能深的搜索树的分支。当节点v的所在边都已都已被搜索过或者在搜索时节点不满足条件,搜索将回溯到发现节点v的那条边的起始节点。整个进程反复进行直到所有节点都被访问为止。属于盲目搜索,最糟糕的情况下算法的时间复杂度为O(n!)。
做一个形象的比喻:dfs好像是在走迷宫,得一直走到头,看看路的尽头是不是出口,如果是,就直接走出去;如果不是,那就返回上一个“标记点”寻找不一样的可行方法。dfs的实现关键在于回溯。
过程
(1)从图中某个顶点v出发,访问v。
(2)找出刚访问过的顶点的第一个未被访问的邻接点,访问该顶点。以该顶点为新顶点,重复此步骤,直至刚访问过的顶点没有未被访问的邻接点为止。
(3)返回前一个访问过的且仍有未被访问的邻接点的顶点,找出该顶点的下一个未被访问的邻接点,访问该顶点。
(4)重复步骤(2)和(3),直至图中所有顶点都被访问过,搜索结束。
图解
假设从0开始访问所有节点:
编辑
首先访问6,然后访问8,由于8的邻接点都已被访问,此步结束。
编辑
然后回到6,再回到0,访问5,访问2。
编辑
回到5,访问4。
编辑
依次返回5,0,开始访问1,3,7,走完所有节点。
编辑
DFS路径:0->6->8->5->2->4->1->3->7
模板
int dfs(int u)
{
st[u] = true; // st[u] 表示点u已经被遍历过for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (!st[j]) dfs(j);
}
}
代码
#include<iostream>
using namespace std;
int visit[101],count,n,g[101][101];
void dfs(int cur){
int i;
cout<<cur<<" ";
for(int i=1;i<=n;i++){
if(g[cur][i]==1 && visit[i]==0){
visit[i]=1;
dfs(i);
}
}
}
int main(){
int i,j,m,a,b;
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>a>>b;
g[a][b]=1;
g[b][a]=1;
}
visit[1]=1;
dfs(1);
return 0;
}
适用范围
1.排列组合问题
2.N*N走迷宫问题
3.连通块
3 广度优先搜索(BFS)
广度优先搜索(Breadth First Search,BFS):属于一种盲目搜寻法,目的是系统地展开并检查图中所有的节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。因为所有的节点必须被储存,因此BFS的空间复杂度为O(
),其中
是节点的数目,而
是图中边的数目。
再来一个形象的比喻:你是一个高度近视者,有一次起床,你的眼镜找不到了,于是你就在四周搜索,直到慢慢找出你的眼镜。BFS一般用队列实现,因为队列先进先出的模式很契合BFS的判定方式——即先遍历所有可行的下一步,再放入队中,再从队顶一个一个判定结果,循环执行直到得到结果。
过程
(1)从图中某个顶点v出发,访问v。
(2)依次访问v的各个未曾访问过的邻接点。
(3)分别从这些邻接点出发依次访问它们的邻接点,并使“先被访问的顶点的邻接点”先于“后被访问的顶点的邻接点”被访问。重复步骤(3),直至图中所有已被访问的顶点的邻接点都被访问到。
图解
以之前的图为例,还是从0出发。
编辑
首先访问节点0的邻接点1,5,6,8.
编辑
然后按次序,先访问1的邻接点3,再访问5的邻接点2和4,由于节点6和8没有未被访问过的邻接点,此步结束。至此所有节点都已被访问过。
编辑
BFS路径:0->1->5->6->8->3->7->2->4
模板
queue q;
st[1] = true; // 表示1号点已经被遍历过
q.push(1);while (q.size())
{
int t = q.front();
q.pop();for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (!st[j])
{
st[j] = true; // 表示点j已经被遍历过
q.push(j);
}
}
}
代码
#include<iostream>
#include<queue>
using namespace std;
int visit[101],cnt,n,g[101][101];
int main(){
int i,j,m,a,b;
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>a>>b;
g[a][b]=1;
g[b][a]=1;
}
visit[1]=1;
queue<int> q;
cnt=1;
q.push(1);
cout<<1<<" ";
while(!q.empty() && cnt<n){
int cur=q.front();
for(int i=1;i<=n;i++){
if(g[cur][i]==1 && visit[i]==0){
cout<<i<<" ";
q.push(i);
cnt++;
visit[i]=1;
}
if(cnt>=n)
break;
}
q.pop();
}
return 0;
}
适用范围
1.最短距离问题
2.大范围查找
3.层序遍历
4 DFS与BFS的区别
| 深度优先搜索(DFS) | 广度优先搜索(BFS) |
| 1.DFS从根节点开始搜索,并从根节点尽可能远地探索这些节点。2.使用堆栈数据结构来记住下一个节点访问。3.DFS所需的内存少于BFS所需的内存。4.它是通过LIFO列表应用的。5.寻找最短距离的理想选择。6.该算法用于解决问题,拓扑排序,需要对图进行回溯,识别图中的循环以及发现两个节点之间的路径等。 | 1.BFS从根节点开始搜索,并根据树级别模式探索所有邻居根。2.它使用队列数据结构来记住下一个节点访问。3.BFS比DFS需要更多的内存。4.它是使用FIFO列表应用的。5.寻找最短路径的理想选择。6.该算法用于查找两个节点之间的最短路径,发现图中所有的连接组件,分析图是否为二部图等。 |
如有错误和不足,敬请指正。