美文网首页程序员技术栈
算法: 寻找图里的强连通组件

算法: 寻找图里的强连通组件

作者: 写代码的海怪 | 来源:发表于2019-02-12 01:55 被阅读9次

强连通图

先说说图里的强连通组件是什么鬼,在说这个东西之前我们先理解一下强连通图。下面就是一张强连通图。

在强连通图里,每个节点都能通过某条路径访问到所有节点。以上面图为例,0, 1, 2, 3 都能互相访问。

强连通组件

组件就是图中的一部分,由图里的某些节点和边组成。再加上强连通的定义,在这一部分图里每个节点都是可以访问的。具体例子参考下图:

这个图里有 3 个强连通组件,(0, 1, 2),(3) 和 (4),注意单个节点也是算一个强连通组件的,因为自己能访问自己嘛。说到这就得提一下强连通组件的一些特点了。

特点

  1. 自己连通,就像刚刚所说的自己访问自己。数学语言是存在一个节点 V,V -> V 的路径长度为0
  2. 交换特性,如果 V 能通过某条路径访问 W V -> W,那么也可以有一条路径使得 W -> V
  3. 传递性,如果 U 能访问 V,且 V 能访问 W U -> V -> W,那么 U 也能访问 W U -> W

寻找强连通组件

我们可以发现强连通组件就算是将它们的边反过来,它还是一个强连通组件。所以我们不禁想到,正向遍历一次再反向遍历一次,如果发现这些节点还是可以互相访问那么就说明这是一个强连通组件了。下面说说具体步骤。

正向遍历

就像刚刚所说的使用 DFS 从图里每个节点去遍历整个图,同时每次访问到一个新节点使用 Stack 去存放这个节点。

当然 DFS 是需要一个额外空间去存放已经访问过的节点。

以上面为例,DFS 结果是 [0, 3, 4, 2, 1],Stack 里的顺序是 [1, 2, 4, 3, 0],其中节点 1 在最顶层。

将图取反

遍历完了之后就要将整个图里的边变成相反方向,如下图所示。

反向遍历

还是使用 DFS 去遍历图,但是不同的地方是遍历的根节点不是随机取了,而是从 Stack 里取。每次从 Stack 里弹出一个节点,就对这个节点进行 DFS。如果遍历过程中发现访问到遍历之前的节点说明找到一个强连通组件了。

伪代码

function forwardDFS(vertex, visited) {
    // 已经访问过了
    visited[vertex] = true
    // 遍历子节点
    for (let child in vertex.children) {
        if (!visited[child]) {
            forwardDFS(child, visited)
        }
    }
    Stack.push(vertex)
}

function reverseGraph(oldGraph) {
    let reversedGraph = new Graph
    // 遍历新图的节点
    for (let vertex in reversedGraph.vertices) {
        // 获取旧图里节点的子节点
        for (let child in oldGraph.vertexOf(vertex).children) {
            // 新图节点的子节点就是旧图的父节点 (取反)
            reversedGraph.vertexOf(child).children.push(vertex)
        }
    }

    return reversedGraph
}

function backwardDFS(vertex, visited) {
    visited[vertex] = true
    for (let child in vertex.children) {
        if (!visited[child]) {
            backwardDFS(child, visited)
        }
    }
}

function findSCCs(graph) {
    // 初始化全部都没访问过
    visited = [false]

    // 正向遍历整个图
    for (let vertex in graph.vertices) {
        if (!visited[vertex]) {
            forwardDFS(vertex, visited)
        }
    }

    // 取反整个图
    reversedGraph = reverseGraph(graph)

    // 初始化全部都没访问过
    visited = [false]

    // 按 Stack 顺序去反向遍历
    while (!stack.isEmpty()) {
        let vertex = stack.pop()

        if (!visited[vertex]) {
            backwardDFS(vertex, visited)
        }
    }
}

相关文章

  • 算法: 寻找图里的强连通组件

    强连通图 先说说图里的强连通组件是什么鬼,在说这个东西之前我们先理解一下强连通图。下面就是一张强连通图。 在强连通...

  • 最小生成树_Prim_Kruskal

    图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有...

  • 数据结构与算法--图论之寻找连通分量、强连通分量

    数据结构与算法--图论之寻找连通分量、强连通分量 寻找无向图的连通分量 使用深度优先搜索可以很简单地找出一幅图的所...

  • 强连通算法

    前言:我们伟大的BAT承载了多少程序员的梦想,到底有多少人的向往... 然而这个“T”的面试也是经常不走寻常路,最...

  • Kosaraju算法详解

    Kosaraju算法是干什么的? Kosaraju算法可以计算出一个有向图的强连通分量 什么是强连通分量? 在一个...

  • tarjan

    tarjan:寻找出度为0的强连通分量,并求出该强连通分量中有多少个点。 sig表示的是强连通分量的个数其中col...

  • 强连通分量和Kosaraju算法

    内容概要: 基于深度优先后序遍历的DAG图拓扑排序 强连通分量 求解强连通分量Kosaraju算法 拓扑排序的另一...

  • 克鲁斯卡尔Kruskal算法使用并查集+优先级队列实现

    kruskal算法 克鲁斯卡尔算法是一种用来寻找连通图中最小生成树的算法。 连通图:在无向图中,若任意两个顶点vi...

  • tarjan

    tarjan寻找出度为0的强连通分量,从小到大输出此强连通分量中的点 poj 2553 The Bottom of...

  • tarjan-寻找图中有多少个强连通分量

    tarjan寻找图中有多少个强连通分量 hdu 1269 迷宫城堡判断图否是属于一个强连通分量

网友评论

    本文标题:算法: 寻找图里的强连通组件

    本文链接:https://www.haomeiwen.com/subject/imlwsqtx.html