20.拓扑排序与欧拉图

一、拓扑排序

1.简介

拓扑排序的英文名是 Topological sorting。拓扑排序要解决的问题是给一个图的所有结点排序,使得排在前面的结点不能依赖于排在后面的结点。

在一个 D A G DAG DAG(有向无环图)中,我们将图中的顶点以线性方式进行排序,使得对于任何的顶点 u u u v v v 的有向边 ( u , v ) (u,v) (u,v), 都可以有 u u u v v v 的前面。若给定一个 D A G DAG DAG,如果从 i i i j j j 有边,则认为 j j j 依赖于 i i i 。如果 i i i j j j 有路径,则称 i i i 间接依赖于 j j j

若是有环图,则不存在拓扑排序,原因显而易见。

2. 求解拓扑排序

  • 从图中选择一个入度为零的点。
  • 输出该顶点,从图中删除此顶点及其所有的出边。
  • 重复上面两步,直到所有顶点都输出,拓扑排序完成。
  • 若图中不存在入度为零的点,此时说明图是有环图,拓扑排序无法完成,陷入死锁。

为了完成这个程序,我们还需要利用 S T L STL STL 库中的队列,同时我们也要注意在拓扑排序中所应用的 B F S BFS BFS 的思想。

3.核心代码

void topu()
{
	queue<ll> q;
	for(ll i=1;i<=n;i++)
		if(du[i]==0)
			q.push(i);
	while(!q.empty())
	{
		ll u=q.front();
		q.pop();
		cout<<u<<" ";
		for(ll i=p[u];i!=-1;i=e[i].next)
		{
			ll v=e[i].v;
			du[v]--;
			if(du[v]==0)
				q.push(v);
		}
	}
}

算法的时间复杂度为 O ( V + E ) O(V+E) O(V+E)

二、欧拉图

1.概念

  • 欧拉回路:通过图中每条边恰好一次的回路。
  • 欧拉路径:通过图中每条边恰好一次的路径。
  • 欧拉图:具有欧拉回路的图。
  • 半欧拉图:具有欧拉通路但不具有欧拉回路的图。

2.性质

(1)无向图

  • 若一个无向图 G G G 存在欧拉路径,当且仅当 G G G 恰有 0 0 0 2 2 2 个奇度顶点

  • 若一个无向图 G G G 存在欧拉回路,当且仅当 G G G 不存在奇度顶点且连通

  • 若一个无向图 G G G 是包含两个奇度顶点的连通图时, G G G 的欧拉路径必定以这两个点为端点

(2)有向图

  • 若一个有向图 G G G 存在欧拉路径,当且仅当 G G G 是弱连通(将有向边看作无向边后连通)有向图,且满足以下两个条件之一:

    • 所有顶点的入度和出度相同
    • 有一个顶点相差 1 1 1,有一个顶点相差 − 1 -1 1,其他顶点的入度和出度相同
  • 若一个有向图 G G G 存在欧拉回路,当且仅当 G G G 是强连通有向图且所有顶点的入度和出度相同

  • 若一个有向图 G G G 包含两个入度和出度不相同的顶点且有欧拉路径时, G G G 的欧拉路径必定以这两个点为端点

3.求出欧拉路径

(1)无向图

计算无向图的欧拉路经可以用“套圈法”、基于深度优先搜索来实现。 从一个合适的顶点出发进行深度优先搜索。当搜到一个顶点 u u u 时:

  1. 如果此时没有顶点与该顶点相连,那么就将 u u u 加入路径中并回溯
  2. 如果有顶点 v v v 与顶点 u u u 相连,那么就删除无向边 < u , v > <u,v> <u,v>,并继续搜索顶点 v v v 。将所有和 u u u 相邻的顶点遍历完之后,将 u u u 加入路径中。
void dfs(ll u)
{
	if(du[u]>0)
	{
		for(ll v=1;v<=n;v++)
		{
			if(mp[u][v])
			{
				mp[u][v]--;
				mp[v][u]--;
				du[u]--;
				dfs(v);
			}
		}
	}
	ans[++cnt]=u;
}

(2)有向图

方法完全一致,只是因为从无向图改为有向图,所以需要改用链式前向星(结点少的时候也可以用邻接矩阵)。然而在最坏的情况下,此算法的时间复杂度为 O ( n 2 ) O(n^2) O(n2)

void dfs(ll u)
{
	for(ll i=p[u];i!=-1;i=e[i].next)
	{
		ll v=e[i].v;
		if(vis[i])
			continue;
		vis[i]=true;
		dfs(v);
	}
	ans[++cnt]=u;
}

这里可以对链式前向星做一定的优化,但这个方式其实并没有让它的时间复杂度完全下降。

void dfs(ll u)
{
	for(ll i=p[u];i!=-1;i=e[i].next)
	{
		ll v=e[i].v;
		if(vis[i])
			continue;
		vis[i]=true;
		p[u]=e[i].next; 
		dfs(v);
	}
	ans[++cnt]=u;
}

最终优化方式如下所示,此方法可以大大降低时间复杂度,在最坏情况下,时间复杂度为 O ( n ) O(n) O(n)

void dfs(ll u)
{
	for(ll i=p[u];i!=-1;i=p[u])
	{
		ll v=e[i].v;
		p[u]=e[i].next;
		if(vis[i])
			continue;
		vis[i]=true;
		dfs(v);
	}
	ans[++cnt]=u;
}

三、作业

1.拓扑

B3644 【模板】拓扑排序 / 家谱树

P4017 最大食物链计数

P4089 [USACO17DEC]The Bovine Shuffle S

2.欧拉图

P1341 无序字母对

P2731 [USACO3.3]骑马修栅栏 Riding the Fences

P7771 【模板】欧拉路径

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值