DAY_11(强连通分量)

本文介绍了强连通分量的概念,探讨了如何使用暴力方法和Kosaraju、Tarjan算法高效求解有向图中的强连通分量。Kosaraju通过反向图和两次dfs确定scc数量,而Tarjan则在一次dfs中划分。文章提供了具体的C++代码示例,展示了这两种算法在不同场景的应用。

求解强连通分量有两种算法,tarjan和kosaraju

首先来俩概念:

强连通:在有向图G中,若两个点u、v是互相可达的,则称u和v是连通的。如果G中任意两个点都是互相可达的,称G是强连通图

强连通分量:如果一个有向图G不是强连通图,那么可以把它分成多个子图,其中每个子图的内部是强连通的,而且这些子图已经扩展到最大,不能与子图外的任意点强连通,称这样一个“极大强连通”子图是G的一个强连通分量(Strongly Connected Component)

有个常见的问题:求G中有多少个scc?

我们先来尝试着把每个scc抽象成一个个的群岛,群岛由有着很多或很少的小岛组成,群岛内部连通;群岛与其他群岛之间有单向道路连接,且不会形成环路。然后将每一个群岛虚拟成一个个点,最后得到一个有向无环图GG,GG中点的数量即为scc数量(还能推出:每个群岛挖掉后,并不会影响其他岛内部的连通性)

那么一开始想到的一定是暴力!!!(暴力不可取。。。)

暴力思路:对每个点求连通性,然后进行比较,互相连通的点就组成了scc(dfs或bfs可实现)

。。。时间复杂度:O(n^2+m)

下面介绍Kosaraju和Tarjan算法(时间复杂度:O(n+m)

Kosaraju

原理:

1、将有向图G的所有边反向构建反图rG,rG不会改变原图G的强连通性。(人话:G的scc数量和rG的数量相同)

2、对G和rG各做一次dfs,可以确定scc数量

有一个我之前特别困惑的点:dfs干啥用的?

——对虚拟点进行拓扑排序,排序后将优先级最高的虚拟点进行反dfs,就可以求出被群岛与外界隔离的那些小岛了

模版在下面\forall \forall \forall \forall

Tarjan

Kosaraju是在图中一个一个将scc给挖出来,而Tarjan能在一次dfs就将所有点按照scc分开

其实理解了Kosaraju,来理解Tarjan也不是很难(个人意见)

有几个变量需要注意:

low[i]:表示能返回到的最远祖先

num[i]:表示第几次循环遍历到i(不太重要的样子

步骤:首先dfs,赋值每个点的low,然后判断low是否与该点的num值相等,如果相等,那么这个点就是自己的祖先点。额。就没了。。(是不是挺简单的~~)

下面上题目::

B3609 [图论与代数结构 701] 强连通分量

一道模版题,下面依次提供Kosaraju和Tarjan两种做法:

Kosaraju::

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
vector<int>g[N],scc[N];
stack<int>st;
int n,m;
int f[N];
int low[N];
int num[N];
int dfn,cnt;
void dfs(int u)
{
	dfn++;
	low[u]=num[u]=dfn;
	st.push(u);
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i];
		if(!num[v])
		{
			dfs(v);
			l
#include <iostream> #include <vector> #include <algorithm> using namespace std; struct Bridge { int a, b, t; }; // 并查集结构 int parent[10001]; int rank[10001]; int find(int x) { if (parent[x] != x) { parent[x] = find(parent[x]); } return parent[x]; } void unite(int x, int y) { int rootX = find(x); int rootY = find(y); if (rootX != rootY) { if (rank[rootX] > rank[rootY]) { parent[rootY] = rootX; } else if (rank[rootX] < rank[rootY]) { parent[rootX] = rootY; } else { parent[rootY] = rootX; rank[rootX]++; } } } bool same(int x, int y) { return find(x) == find(y); } int main() { int n, m; cin >> n >> m; vector<Bridge> bridges(m); for (int i = 0; i < m; ++i) { cin >> bridges[i].a >> bridges[i].b >> bridges[i].t; } // 按照桥的可用天数从小到大排序 sort(bridges.begin(), bridges.end(), [](const Bridge &a, const Bridge &b) { return a.t < b.t; }); // 初始化并查集 for (int i = 1; i <= n; ++i) { parent[i] = i; rank[i] = 0; } int last_day = 0; int protest_day = -1; int protest_count = 0; // 逐天检查桥的失效情况 for (int i = 0; i < m; ++i) { int day = bridges[i].t; if (day > last_day) { last_day = day; while (i < m && bridges[i].t == day) { int a = bridges[i].a; int b = bridges[i].b; if (!same(a, b)) { unite(a, b); } ++i; } i--; // 检查是否有小岛与其他小岛的连接全部失效 bool all_connected = true; for (int j = 1; j <= n; ++j) { if (find(j) != find(1)) { all_connected = false; break; } } if (!all_connected && protest_day == -1) { protest_day = last_day; } } } if (protest_day == -1) { cout << "0 0" << endl; } else { cout << protest_day << " 1" << endl; } return 0; }
最新发布
05-19
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值