图论简笔(上)——DFS、BFS、Toplogical Sort

图的遍历——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

拓扑排序是基于以上两种搜索方法的应用,它要求有向图中不能有环。
它的两种实现方法分别对应DFSBFS
(以下图为例)
在这里插入图片描述


扫描二维码关注公众号,回复: 5302005 查看本文章

用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算法。

猜你喜欢

转载自blog.csdn.net/weixin_43843835/article/details/86766242