Kosaraju算法求有向强连通分量,缩点后是DAG的拓扑序列(从小到大)

强连通分量分解
对于一个有向图顶点的子集S,如果在S内任取两个顶点uv,都能找到一条从uv的路径,那么
就称
S是强连通的。如果在强连通的顶点集合S中加入其他任意顶点集合后,它都不再是强连通的,
那么就称
S是原图的一个强连通分量(SCC:Strongly Connected Component) 。任意有向图都可以
分解成若干不相交的强连通分量,这就是强连通分量分解。把分解后的强连通分量缩成一个顶点,

就得到了一个DAG(有向无环图)。

Kosaraju算法求有向强连通分量,缩点后是DAG的拓扑序列(从小到大)_第1张图片

强连通分量分解可以通过两次简单的DFS实现。第一次DFS时,选取任意顶点作为起点,遍历所
有尚未访问过的顶点,并在回溯前给顶点标号(
post order,后序遍历) 。对剩余的未访问过的顶
点,不断重复上述过程。
完成标号后,越接近图的尾部(搜索树的叶子),顶点的标号越小。第二次
DFS时,先将所有边
反向,然后以标号最大的顶点为起点进行
DFS。这样DFS所遍历的顶点集合就构成了一个强连通
分量。之后,只要还有尚未访问的顶点,就从中选取标号最大的顶点不断重复上述过程。


Kosaraju算法求有向强连通分量,缩点后是DAG的拓扑序列(从小到大)_第2张图片

正如前文所述,我们可以将强连通分量缩点并得到DAG。此时可以发现,标号最大的节点就属于
DAG头部(搜索树的根)的强连通分量。因此,将边反向后,就不能沿边访问到这个强连通分量
以外的顶点。而对于强连通分量内的其他顶点,其可达性不受边反向的影响,因此在第二次
DFS
时,我们可以遍历一个强连通分量里的所有顶点。
Kosaraju算法求有向强连通分量,缩点后是DAG的拓扑序列(从小到大)_第3张图片

该算法只进行了两次DFS,因而总的复杂度是O(|V|+|E|)

int V; // 顶点数
vector G[MAX_V]; // 图的邻接表表示
vector rG[MAX_V]; // 把边反向后的图
vector 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;
}


你可能感兴趣的:(c++)