文章目录
图的遍历——DFS
深度优先搜索的原理与栈类似,每到达一个节点,对于该节点分支出来的几条路径,一次只选择其中一条继续遍历。当改路线进行的重点时,返回上一节点,换另外一个分支继续遍历。每一次搜索同时只能发现一个点。
(搜索路线示意,所有边的指向都是从左到右)
——模板示意——
以上图为例,构建代码,并输出查询路线。
#include<cstdio>
#include<iostream>
#include<vector>
using namespace std;
vector<int> edge[9];
bool vis[9]={false};
int ct=1;
void dfs(int node)
{
if(ct==8)return;
for(int i=0;i<edge[node].size();++i)
if(vis[edge[node][i]]==false)
{
cout<<"-->"<<edge[node][i];
vis[edge[node][i]]=true;
dfs(edge[node][i]);
}
}
int main()
{
for(int i=1;i<=10;++i)
{
int fr,to;
cin>>fr>>to;
edge[fr].push_back(to);
}
cout<<1;
dfs(1);
return 0;
}
(各个结点的被发现顺序)
例题——信息传递
P2661 信息传递
本题思路:
把题中信息传递的方向看做是结点之间的有向边,可以构建出一个有向图。当从一个结点出发,经过多个结点又可以回到该节点时,则视作这名同学可以被告知自己的生日。所求的答案就可以转化为该图中最小环包含的节点数。
入度为0的点一定不是答案,与入读为0的点相连的的入读为1的点也不是答案,此处可以用dfs删去这些点。如此操作后,图中只剩下几个孤立的环,再对每一个环做一次dfs,找到最小的环。
![在这里插入图片描述](https://img-blog.cs
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 200005
using namespace std;
int n,s[N],vis[N],ans=0xffffff,cz[N];
int main()
{
cin>>n;
memset(vis,0,sizeof(vis));
memset(cz,0,sizeof(cz));
//s[i]记录第i个点的指向
//vis[i]记录第i个点的 被指向点数
for(int i=1;i<=n;++i)
{
cin>>s[i];
++vis[s[i]];
}
//去点操作,最后结果vis数组里只有0和1,0表示被舍弃的点,1表示该点为某一个环的一部分
for(int j=1;j<=n;++j)
{
int i=j;
while(vis[i]==0&&cz[i]==0)
{
cz[i]=1;
--vis[s[i]];
i=s[i];
}
}
for(int j=1;j<=n;++j)
{
int i=j;
int ct=0;
int flag=0;
while(vis[i]!=0)
{
flag=1;
--vis[i];
i=s[i];
++ct;
}
if(flag&&ans>ct)
ans=ct;
}
cout<<ans<<endl;
return 0;
}
图的遍历——BFS
广度优先搜索基于队列实现,每到达一个新的结点,同时对该节点所指向的点一起搜索,直到搜索结束。在解决无权图中可以用来求解最短路径。
(如图,2、3被同时搜索到,4、5被同时搜索到,6、7、8被同时搜索到)
#include<cstdio>
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
vector<int> edge[9];
queue<int> q;
bool vis[9]={false};
int ct=1;
void Bfs()
{
while(!q.empty())
{
int node=q.front();
q.pop();
for(int i=0;i<edge[node].size();++i)
if(vis[edge[node][i]]==false)
{
q.push(edge[node][i]);
vis[edge[node][i]]=true;
}
}
}
int main()
{
for(int i=1;i<=10;++i)
{
int fr,to;
cin>>fr>>to;
edge[fr].push_back(to);
}
q.push(1);
Bfs();
return 0;
}
例题——寻找道路
P2296 寻找道路
本题思路和解法参考我之前写的博客中的T2
文章链接
图的遍历——Toplogical Sort
拓扑排序是基于以上两种搜索方法的应用,它要求有向图中不能有环。
它的两种实现方法分别对应DFS和BFS。
(以下图为例)
用DFS实现Toplogical Sort
该方法利用无环图的性质,即必存在某个点的出度为0,然后从终点往起点把遍历到的结点依次进行递归,而递归结束的点加入到答案队列。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
vector<int> edge[14];
bool vis[14];
int ct[14];
int cnt=0;;
int ans[13];
void dfs(int node)
{
vis[node]=true;
for(int i=0;i<edge[node].size();++i)
{
if(vis[edge[node][i]]==false)
dfs(edge[node][i]);
}
ans[++cnt]=node;//当一个点的所有支路完全被访问完后,再加入序列
}
int main()
{
for(int i=1;i<=15;++i)
{
int fr,to;
cin>>fr>>to;
edge[to].push_back(fr);
++ct[fr];
}
for(int i=1;i<=13;++i)
if(ct[i]==0)
dfs(i);
for(int i=1;i<=13;++i)
cout<<ans[i]<<" ";
return 0;
}
拓扑排序的结果:1 2 3 4 7 5 6 9 8 10 11 12 13
用BFS实现Toplogical Sort
由于图中无环,必存在某些点的入度为0,把这些点加入队列,并同时搜索,与入度为0的点相邻的结点入读减1,变换后入度为0的点继续加入队列搜索。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
vector<int> edge[14];
queue<int> q;
int ct[14];
int cnt=0;;
int ans[13];
void bfs()
{
while(!q.empty())
{
int node=q.front();
ans[++cnt]=node;
q.pop();
for(int i=0;i<edge[node].size();++i)
{
--ct[edge[node][i]];
if(ct[edge[node][i]]==0)
q.push(edge[node][i]);
}
}
}
int main()
{
for(int i=1;i<=15;++i)
{
int fr,to;
cin>>fr>>to;
edge[fr].push_back(to);
++ct[to];
}
for(int i=1;i<=13;++i)
if(ct[i]==0)
q.push(i);
bfs();
for(int i=1;i<=13;++i)
cout<<ans[i]<<" ";
return 0;
}
拓扑结果:1 2 7 3 8 5 4 10 6 9 11 12 13
例题——欧拉的拓扑排序(字典序)
来源:2016 NUIST 程序设计竞赛 E题
——描述——
欧拉是科学史上最多产的一位杰出的数学家,他一生大部分时间在俄国和普鲁士度过。欧拉患上了眼病被赶出了普鲁士,但是俄国接纳了他。欧拉完全失明以后,仍然以惊人的毅力与黑暗搏斗,凭着记忆和心算进行研究完成了一生中将近一半的著作。欧拉的两个学生把一个复杂的收敛级数的17项加起来,算到第50位数字,两人相差一个单位,欧拉为了确定究竟谁对,用心算进行全部运算,最后把错误找了出来。欧拉在数学上的建树很多,对著名的哥尼斯堡七桥问题的解答开创了图论的研究。
欧拉留下的是关于图的问题,人们做事情总有不同的顺序,我们可以有向图的方式来抽象表示这种关系,拓扑排序就是将这些顺序线性表示出来。
——输入——
第一行两个整数n,m.表示一共有n个不同的节点,m条边接下来m行,每行两个数据v和t,表示有向边从v到t
——输出——
输出一行:字典序最大的拓扑排序结果。注意拓扑排序可能存在很多种结果,输出字典序最大的一组就可以了。最后一个结果后面没有空格,直接回车。
——样例输入——
3 2
1 2
2 3
——样例输出——
1 2 3
思路:本题基本上是Toplogical Sort的模板题,但是多了一个条件,要求输出字典序最大的序列,此时可以利用bfs的性质,把入度为0的点加入队列的同时进行插入排序,而priority_queue正好可以满足这一需求。
代码演示:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
vector<int> edge[10000];
priority_queue<int> q;
int ct[10000];
int cnt=0;
int ans[10000];
int n,m;
void bfs()
{
while(!q.empty())
{
int node=q.top();
ans[++cnt]=node;
q.pop();
for(int i=0;i<edge[node].size();++i)
{
--ct[edge[node][i]];
if(ct[edge[node][i]]==0)
q.push(edge[node][i]);
}
}
}
int main()
{
while(cin>>n>>m)
{
for(int i=1;i<=n;++i)
edge[i].clear();
while(!q.empty())
q.pop();
memset(ans,0,sizeof(ans));
memset(ct,0,sizeof(ct));
cnt=0;
for(int i=1;i<=m;++i)
{
int fr,to;
cin>>fr>>to;
edge[fr].push_back(to);
++ct[to];
}
for(int i=1;i<=n;++i)
if(ct[i]==0)
q.push(i);
bfs();
for(int i=1;i<=n-1;++i)
cout<<ans[i]<<" ";
cout<<ans[n]<<endl;
}
return 0;
}
总结
上篇整理了图论中最简单的搜索方式,在后面的篇章中会整理计算最短路的Bellman-Ford算法、Dijkstra算法、Floyd-Warshall算法,以及计算最小生成树的Prim算法和Kruskal算法。