定义:
如果两个顶点可以相互通达,则称两个顶点强连通 (strongly connected)。如果有向图 G 的每两个顶点都强连通,称 G 是一个强连通图。有向图的极大强连通子图,称为强连通分量 (strongly connected components)。
Tarjan算法
- 复杂度
可以发现,运行 tarjan 算法的过程中,每个顶点都被访问了一次,且只进出了一次堆栈,每条边也只被访问了一次,所以该算法的时间复杂度为 O (n+e)。其中 n 为有向图 G 的顶点数量,e 为 G 的边数。
代码
#include<iostream>
#include<vector>
using namespace std;
const int mxn = 5010;
int Stack[mxn], top = 0; //Tarjan 算法中的栈
bool InStack[mxn]; //某个节点是否在栈中
int DFN[mxn]; //递归搜索到这个节点时候的 次序/编号
int Low[mxn]; //能够追溯到的最早的 次序/编号
int ComponentNum = 0; //强连通分量的个数
int Index = 0; //次序/编号/索引号
vector<int> edge[mxn]; //存图
vector<int> component[mxn]; //每个强连通分量的组成
int InComponent[mxn]; //记录每个节点所在的强连通分量
int ComponentDegree[mxn]; //记录每个强连通分量的度
void Tarjan(int u)
{
DFN[u] = Low[u] = ++ Index;
InStack[u] = true;
Stack[++ top] = u;
int v;
for(int i = 0; i < edge[i].size(); i ++)
{
v = edge[u][i];
if(! DFN[v])
{
Tarjan(v);
Low[u] = min(Low[u], Low[v]);
}
else if(InStack[v]) Low[u] = min(Low[u], DFN[v]); //也可以是 Low[u] = min(Low[u], Low[v]); 仅在求强连通分量的时候
}
if(DFN[u] == Low[u])
{
ComponentNum ++;
do
{
v = Stack[top --];
InStack[v] = false;
component[ComponentNum].push_back(v);
InComponent[v] = ComponentNum;
}
while(u != v);
}
}
void init(int n)
{
Index = 0;
for(int i = 1; i <= n; i ++) component[i].clear(), Low[i] = DFN[i] = 0;
}
int main()
{
int n, m;
while(scanf("%d %d", &n, &m) & n)
{
int u, v;
for(int i = 1; i <= m; i ++)
scanf("%d %d", &u, &v), edge[u].push_back(v);
for(int i = 1; i <= n; i ++)
if(! DFN[i]) Tarjan(i);
}
return 0;
}
Kosaraju算法——两遍dfs
-
复杂度
由于算法的时间取决于两次 DFS,因此时间复杂度,对于稀疏图是 O (V+E),对于稠密图是 O (V²),
代码(待添加)
#include<iostream>
#include<vector>
using namespace std;
const int mxn = 1e5;
vector<int> G[mxn];
vector<int> G_t[mxn];
int n, m, idx;
int vis[mxn], tim[mxn];
void dfs(int u)
{
vis[u] = 1;
for(int i = 0; i < G[u].size(); i ++)
{
int v = G[u][i];
if(! vis[v])
dfs(v);
}
tim[++ idx] = u;
}
void rdfs(int u)
{
vis[u] = 1;
for(int i = 0; i < G_t[u].size(); i ++)
{
int v = G_t[u][i];
if(! vis[v])
rdfs(v);
}
}
void init(int n)
{
idx = 0;
memset(vis, 0, sizeof(vis));
for(int i = 1; i <= n; i ++)
G[i].clear(), \
G_t[i].clear();
}
int main()
{
while(scanf("%d %d", &n, &m) != EOF && n)
{
init(n);
for(int i = 1, u, v; i <= m; i ++)
{
scanf("%d %d", &u, &v);
G[u].push_back(v);
G_t[v].push_back(u);
}
for(int i = 1; i <= n; i ++)
{
if(! vis[i])
dfs(i);
}
memset(vis, 0, sizeof(vis));
int sum = 0; //强连通分量 的数量
for(int i = n; i >= 1; i --)
{
if(! vis[tim[i]])
rdfs(tim[i]), sum ++;
}
printf("%d\n", sum);
}
return 0;
}
/*
12
16
12 11
11 8
11 10
8 10
10 9
9 8
9 7
7 6
5 7
6 5
6 4
6 3
4 3
2 3
3 2
4 1
*/
Gabow算法
- 讲解
算法讲解 1
- 复杂度
O (v + e),Tarjan 算法和 Gabow 算法其实是同一个思想的不同实现,但是,Gabow 算法更精妙,时间更少 (不用频繁更新 Low [])。
代码
#include<iostream>
#include<vector>
#include<stack>
using namespace std;
const int mxn = 1e5;
vector<int> edge[mxn]; //邻接表存图
int order[mxn]; //存储节点被递归遍历的到的次序,注意这个次序号的 大小 是作为判断:强连通分量的 根的依据
int idx = 0; //遍历的次序
int part[mxn]; //某个节点是否已经被确定的了 在某个强连通分量中
int partnum = 0; //强连通分量的 编号
stack<int> path; //记录路径
stack<int> root; //存储并不断的找,强连通分量的根
void Gabow(int u)
{
order[u] = ++ idx;
path.push(u);
root.push(u);
for(int i = 0; i < edge[u].size(); i ++)
{
int v = edge[u][i];
if(order[v] == -1) //v 没有被拜访过/编过号
Gabow(v);
else if(part[v] == -1) //v 被拜访过,但是它所位于的强连通分量 还没有被分配编号
{
while(order[root.top()] > order[v]) root.pop();
}
}
if(u == root.top()) //从节点u-> 出发 经过一些节点又回到了 -> u 说明找到一个 强连通分量
{
root.pop();
partnum ++; //为找到的 这个强连通分量 分配编号
int Top;
do
{
Top = path.top();
path.pop();
part[Top] = partnum;
} while(Top != u);
}
}
void init(int n)
{
idx = partnum = 0;
for(int i = 1; i <= n; i ++) order[i] = -1, part[i] = -1, edge[i].clear();
}
int main()
{
return 0;
}
例题 A - 迷宫城堡 HDU - 1269
Tarjan代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#include <string>
#include <queue>
#include <map>
/* #include <unordered_map> */
#include <bitset>
#include <vector>
#include <stack>
void fre() { system("clear"), freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); }
void Fre() { system("clear"), freopen("A.txt", "r", stdin);}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define ll long long
#define ull unsigned long long
#define db double
#define Pir pair<int, int>
#define PIR pair<Pir, Pir>
#define m_p make_pair
#define INF 0x3f3f3f3f
#define esp 1e-7
#define mod (ll)(1e9 + 7)
#define for_(i, s, e) for(int i = (ll)(s); i <= (ll)(e); i ++)
#define rep_(i, e, s) for(int i = (ll)(e); i >= (ll)(s); i --)
#define sc scanf
#define pr printf
#define sd(a) scanf("%d", &a)
#define ss(a) scanf("%s", a)
using namespace std;
const int mxn = 10005;
const int mxm = 100005;
struct Edge
{
int v, next;
} edge[mxm];
int head[mxn], vis[mxn];
stack<int> st;
int num, idx, tot;
int Low[mxn], DFN[mxn];
vector<int> res[mxn];
void Add(int u, int v)
{
edge[++ tot] = (Edge){ v, head[u] }; head[u] = tot;
}
bool Tarjan(int u)
{
Low[u] = DFN[u] = ++ idx;
vis[u] = 1; //是否在 栈 里面
st.push(u);
for(int i = head[u]; i; i = edge[i].next)
{
int v = edge[i].v;
if(! vis[v])
{
Tarjan(v);
Low[u] = min(Low[u], Low[v]); //注意这的一是 min(Low[u], Low[v]) 这与Low[v] 的值会变小有关
}
else if(vis[v])
{
Low[u] = min(Low[u], DFN[v]);
}
}
if(Low[u] == DFN[u])
{
num ++;
if(num >= 2) return false;
int Top;
do
{
Top = st.top();
st.pop();
vis[Top] = false;
/* res[num].pb(Top); */
} while(Top != u);
}
if(num >= 2) return false;
return true;
}
void init(int n)
{
num = idx = tot = 0;
for_(i, 1, n) DFN[i] = Low[i] = vis[i] = head[i] = 0; //不要忘了给 head 数组进行初始化
while(! st.empty()) st.pop();
}
int main()
{
//fre();
int n, m;
while(sc("%d %d", &n, &m) && n)
{
init(n);
int u, v;
for_(i, 1, m)
{
sc("%d %d", &u, &v);
Add(u, v);
}
int fg = 1;
for_(i, 1, n)
{
if(DFN[i] == 0)
{
if(Tarjan(i) == false || i > 1)
{
fg = 0;
break;
}
}
}
if(fg)
pr("Yes\n");
else
pr("No\n");
}
return 0;
}
Kosaraju-两遍dfs 代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#include <string>
#include <queue>
#include <map>
/* #include <unordered_map> */
#include <bitset>
#include <vector>
#include <stack>
void fre() { system("clear"), freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); }
void Fre() { system("clear"), freopen("A.txt", "r", stdin);}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define ll long long
#define ull unsigned long long
#define db double
#define Pir pair<int, int>
#define PIR pair<Pir, Pir>
#define m_p make_pair
#define INF 0x3f3f3f3f
#define esp 1e-7
#define mod (ll)(1e9 + 7)
#define for_(i, s, e) for(int i = (ll)(s); i <= (ll)(e); i ++)
#define rep_(i, e, s) for(int i = (ll)(e); i >= (ll)(s); i --)
#define sc scanf
#define pr printf
#define sd(a) scanf("%d", &a)
#define ss(a) scanf("%s", a)
using namespace std;
const int mxn = 10005;
const int mxm = 100005;
struct Edge
{
int v, next;
} e1[mxm], e2[mxm];
int t1, t2;
int h1[mxn], h2[mxn];
void Add(int u, int v, int & tot, int h[], Edge e[]) //注意这里 tot 的声明必须是 引用!!!
{
e[++ tot] = (Edge){ v, h[u] }; h[u] = tot;
}
int idx, vis[mxn], tim[mxn];
void dfs(int u)
{
vis[u] = 1;
for(int i = h1[u]; i; i = e1[i].next)
{
int v = e1[i].v;
if(! vis[v])
dfs(v);
}
tim[u] = ++ idx;
}
void rdfs(int u)
{
vis[u] = 1;
for(int i = h2[u]; i; i = e2[i].next)
{
int v = e2[i].v;
if(! vis[v])
rdfs(v);
}
}
void init(int n)
{
t1 = t2 = idx = 0;
for_(i, 1, n) h1[i] = h2[i] = vis[i] = 0;
}
int main()
{
/* fre(); */
int n, m;
while(sc("%d %d", &n, &m) && n)
{
init(n);
int u, v;
for_(i, 1, m)
{
sc("%d %d", &u, &v);
Add(u, v, t1, h1, e1);
Add(v, u, t2, h2, e2);
}
for_(i, 1, n)
if(! vis[i])
dfs(i);
for_(i, 1, n) vis[i] = 0; //注意这里的初始化不要忘
int fg = 0;
for_(i, 1, n)
if(! vis[i])
{
rdfs(i);
fg ++;
if(fg >= 2)
break;
}
if(fg == 2)
pr("No\n");
else
pr("Yes\n");
}
return 0;
}
Gbow代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#include <string>
#include <queue>
#include <map>
/* #include <unordered_map> */
#include <bitset>
#include <vector>
#include <stack>
void fre() { system("clear"), freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); }
void Fre() { system("clear"), freopen("A.txt", "r", stdin);}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define ll long long
#define ull unsigned long long
#define db double
#define Pir pair<int, int>
#define PIR pair<Pir, Pir>
#define m_p make_pair
#define INF 0x3f3f3f3f
#define esp 1e-7
#define mod (ll)(1e9 + 7)
#define for_(i, s, e) for(int i = (ll)(s); i <= (ll)(e); i ++)
#define rep_(i, e, s) for(int i = (ll)(e); i >= (ll)(s); i --)
#define sc scanf
#define pr printf
#define sd(a) scanf("%d", &a)
#define ss(a) scanf("%s", a)
using namespace std;
const int mxn = 10005;
const int mxm = 100005;
struct Edge
{
int v, next;
} edge[mxm];
int tot, head[mxn];
void Add(int u, int v) { edge[++ tot] = (Edge){ v, head[u] }; head[u] = tot; }
int part[mxn], idx;
int order[mxn], partnum;
stack<int> path;
stack<int> root;
void Gabow(int u)
{
path.push(u);
root.push(u);
order[u] = ++ idx;
for(int i = head[u]; i; i = edge[i].next)
{
int v = edge[i].v;
if(! order[v])
Gabow(v);
else if(! part[v]) //如果被访问过,且在 root栈中,那么v就可能是 v所属的强连通分量的根
{
while(order[root.top()] > order[v]) root.pop();
}
}
if(u == root.top())
{
partnum ++;
root.pop();
int Top;
do
{
Top = path.top();
//vis[Top] = 0;
path.pop();
} while(Top != u);
}
}
void init(int n)
{
tot = idx = partnum = 0;
for_(i, 1, n) head[i] = order[i] = part[i] = 0;
}
int main()
{
/* fre(); */
int n, m;
while(sc("%d %d", &n, &m) && n)
{
init(n);
int u, v;
for_(i, 1, m)
{
sc("%d %d", &u, &v);
Add(u, v);
}
int fg = 0;
for_(i, 1, n)
{
if(! order[i])
{
Gabow(i);
fg += partnum;
partnum = idx = 0; //这里不要忘了初始化
if(fg >= 2)
break;
}
}
if(fg == 2)
pr("No\n");
else
pr("Yes\n");
}
return 0;
}