求有向强连通分量 模板
模板
Kosaraju算法
int V; // 顶点数
vector<int> G[MAX_V]; // 图的邻接表表示
vector<int> rG[MAX_V]; // 把边反向后的图
vector<int> vs; // 后序遍历顺序的顶点列表
bool used[MAX_V]; // 访问标记
int cmp[MAX_V]; // 所属强连通分量的拓扑序322 第 4 章 登峰造极——高级篇
void add_edge(int from, int to)
{
G[from].push_back(to);
rG[to].push_back(from);
}
void dfs(int v)
{
used[v] = true;
for (int i = 0; i < G[v].size(); i++)
{
if (!used[G[v][i]]) dfs(G[v][i]);
}
vs.push_back(v);
}
void rdfs(int v, int k)
{
used[v] = true;
cmp[v] = k;
for (int i = 0; i < rG[v].size(); i++)
{
if (!used[rG[v][i]]) rdfs(rG[v][i], k);
}
}
int scc()
{
memset(used, 0, sizeof(used));
vs.clear();
for (int v = 0; v < V; v++)
{
if (!used[v]) dfs(v);
}
memset(used, 0, sizeof(used));
int k = 0;
for (int i = vs.size() - 1; i >= 0; i--)
{
if (!used[vs[i]]) rdfs(vs[i], k++);
}
return k;
}
输出每个编号强连通
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<string>
#include<vector>
#include<algorithm>
#define INF 99999999
#define MAX_V 10000
using namespace std;
int V;
vector<int>G[MAX_V];
vector<int>rG[MAX_V];
vector<int>vs;
vector<int>SCC[MAX_V];
bool used[MAX_V];
int cmp[MAX_V];
void add_edge(int from,int to){
G[from].push_back(to);
rG[to].push_back(from);
}
void dfs(int v){
used[v]=true;
for(int i=0;i<G[v].size();i++){
if(!used[G[v][i]])dfs(G[v][i]);
}
vs.push_back(v);
}
void rdfs(int v,int k){
used[v]=true;
cmp[v]=k;
SCC[k].push_back(v);
for(int i=0;i<rG[v].size();i++){
if(!used[rG[v][i]])rdfs(rG[v][i],k);
}
}
int scc(){
memset(used,0,sizeof(used));
vs.clear();
for(int i=1;i<V;i++){
if(!used[i])dfs(i);
}
memset(used,0,sizeof(used));
int k=0;
for(int i=vs.size()-1;i>=0;i--){
if(!used[vs[i]])rdfs(vs[i],k++);
}
return k;
}
void init(){//初始化有向图
V=13;
add_edge(4,1);
add_edge(2,3);
add_edge(3,2);
add_edge(4,3);
add_edge(6,3);
add_edge(6,4);
add_edge(6,5);
add_edge(5,7);
add_edge(7,6);
add_edge(9,7);
add_edge(9,8);
add_edge(8,10);
add_edge(10,9);
add_edge(11,8);
add_edge(11,10);
add_edge(12,11);
}
int main(){
init();
int ccount=scc();
for(int i=0;i<ccount;i++){
for(int j=0;j<SCC[i].size();j++){
if(j==0)printf("连通分量%d:%d",i,SCC[i][j]);
else printf(" %d",SCC[i][j]);
}
printf("\n");
}
return 0;
}
tarjan算法
DFN[ i ] : 在DFS中该节点被搜索的次序(时间戳)
LOW[ i ] : 为i或i的子树能够追溯到的最早的栈中节点的次序号
当DFN[ i ]==LOW[ i ]时,为i或i的子树可以构成一个强连通分量。
void add_edge(int u,int v) { edge[u].push_back(v); } void tarjan(int u) { instack[u] = true; low[u] = DFN[u] = ++tot; ss.push(u); //将刚访问的节点入栈 for(int i = 0;i < edge[u].size();i++) { int 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]); } } if(DFN[u] == low[u]) // u 为一个强连通分量的根 { scc++;//强连通分量的编号 int v; do { v = ss.top(); ss.pop(); belong[v] = scc; //标记每个节点所在的强连通分量 num[scc]++; //每个强连通分量的节点个数 }while(u != v); } }
这个貌似比上面的要好一点
vector<int> G[maxn]; int Mark[maxn], Root[maxn], Stack[maxn]; //时间戳,根(当前分量中时间戳最小的节点),栈 bool Instack[maxn]; //是否在栈中标记 int Ssc[maxn]; //每个节点所在的强连通分量的编号 int Index, Ssc_n, Top; //搜索时用的时间戳,强连通分量总数,栈顶指针 void Tarjan(int u) //u 当前搜索到的点 { Mark[u] = Root[u] = ++ Index; //每找到一个点,对时间戳和根初始化 Stack[Top ++] = u; //压栈 Instack[u] = true; //在栈中标记 int v; for(int i= 0; i< G[u].size(); i++) //向下搜索 { v = G[u][i]; if(Mark[v] == 0) //没到过的点 { Tarjan(v); //先向下搜索 if(Root[u] > Root[v]) Root[u] = Root[v]; //更新根 } else if(Instack[v] && Root[u] > Mark[v]) Root[u] = Mark[v]; //到过的点且点仍在栈中,试着看这个点能不能成为根 } /*对当前点的搜索结束*/ if(Mark[u] == Root[u]) //当前点本身时根 { Ssc_n ++; //更新强连通分量数 do{ //栈中比它后入栈的元素在以它为根的强连通分量中 v = Stack[-- Top]; Instack[v] = false; Ssc[v] = Ssc_n; }while(v != u); //直到它自己 } } void SSC() { memset(Mark, 0, sizeof Mark); //初始化时间戳和栈内标记 memset(Instack, false, sizeof Instack); Index = Ssc_n = Top = 0; //初始化时间戳,强连通分量数,栈顶指针 for(int i= 1; i<= N; i++) //保证图上所有点都访问到 if(Mark[i] == 0) Tarjan(i); } /***************************Tarjan算法模板***************************/