【问题标题】:How to reduce a strongly connected component to one vertex?如何将强连通分量减少到一个顶点?
【发布时间】:2018-06-24 09:30:49
【问题描述】:

来自https://algs4.cs.princeton.edu/42digraph/

  1. 有向图中的可达顶点。设计一个线性时间算法来确定一个有向图是否有一个顶点可以从 每隔一个顶点。

Kosaraju-Sharir algorithm 为我们提供了强连接组件。可以看到here 的 Java 代码。将每个 SCC 缩减为一个顶点,一个出度为零的顶点可以相互到达。

问题是,似乎每个人都在谈论减少 SCC 而没有提供细节。这样做的有效算法是什么?

【问题讨论】:

  • 好吧,当k 顶点(k 是 SCC 的数量)和m 边时,您基本上必须构造一个新图:每个“旧”边变成“新”边对应的 SCC 之间。我不确定混淆的根源是什么——算法本身、它的正确性、它的运行时间、实现细节?
  • @yeputons 混乱的根源是缺乏关于“如何”的细节。你所说的并没有增加我已经知道的任何东西。
  • 您可以使用任何算法来确定 SCC。之后,您知道哪个顶点属于哪个 SCC,即您有一个顶点集列表​​。然后创建一个新图,为每个 SCC 添加一个顶点(这是一组顶点)。然后迭代每个 SCC 的每个顶点并添加边。因此,您需要知道另一个方向的映射(顶点到其 SCC)。通常人们使用所谓的 Union-Find (Wikipedia) 来完成所有这些工作。方法应该非常清楚。请提供有关确切问题的更多详细信息。
  • @Zabuza 自​​己已经发布了一个解决方案,你有点晚了。谢谢。
  • @AbhijitSarkar 是的,我明白了。老实说,看起来相当复杂。我已经在三个项目中完成了这项任务。如果您更好地设置SCC 类,会容易得多。也就是说,它应该代表一个Set<Vertex>,并且你应该有一些Map<Vertex, SCC>(或一个联合查找数据结构)。然后就只有 10 行代码了。

标签: algorithm graph directed-graph strongly-connected-graph


【解决方案1】:

假设您已经有一种计算SCCs 的方法以及常用的图、顶点和边方法。然后它只是创建一个新图,为每个 SCC 添加一个顶点代表,然后添加边代表。

对于边,您需要能够将原始顶点(边目的地)映射到其在新图中的代表。您可以在第一遍中使用 Map<Vertex, SCC> 将顶点映射到它们的 SCC 和 Map<SCC, Vertex> 将 SCC 映射到新图中的代表顶点进行建模。或者你直接有一个Map<Vertex, Vertex> 将原始顶点映射到它们的代表。


这是一个 Java 解决方案:

public static Graph graphToSccGraph(Graph graph) {
    Collection<SCC> sccs = SccComputation.computeSccs(graph);
    Graph sccGraph = new Graph();

    Map<Vertex, SCC> vertexToScc = new HashMap<>();
    Map<SCC, Vertex> sccToRep = new HashMap<>();

    // Add a representative for each SCC (O(|V|))
    for (SCC scc : sccs) {
        Vertex rep = new Vertex();
        sccGraph.addVertex(rep);

        sccToRep.put(scc, rep);
        for (Vertex vertex : scc.getVertices()) {
            vertexToScc.put(vertex, scc);
        }
    }

    // Add edge representatives (O(|E|))
    for (Vertex vertex : graph.getVertices()) {
        Vertex sourceRep = sccToRep.get(vertexToScc.get(vertex));
        for (Edge edge : vertex.getOutgoingEdges()) {
           Vertex destRep = sccToRep.get(vertexToScc.get(edge.getDestination()));
           Edge edgeRep = new Edge(sourceRep, destRep);
              if (!sccGraph.contains(edgeRep)) {
                  sccGraph.addEdge(edgeRep);
              }
        }
    }

    return sccGraph;
}

时间复杂度与图的大小(顶点和边的数量)呈线性,因此最优。那是Theta(|V| + |E|)

通常人们使用Union-Find(参见Wikipedia)数据结构来简化此操作并摆脱Maps。

【讨论】:

    【解决方案2】:

    以下是我自己的问题的 Java 解决方案。对于图形表示,它使用来自https://github.com/kevin-wayne/algs4edu.princeton.cs:algs4:1.0.3。如this paper 所述,似乎有用于图形收缩的通用算法;但是,就我的目的而言,以下内容就足够了。

    /**
     * 43. <b>Reachable vertex.</b>
     * <p>
     * DAG: Design a linear-time algorithm to determine whether a DAG has a vertex that is reachable from every other
     * vertex, and if so, find one.
     * Digraph: Design a linear-time algorithm to determine whether a digraph has a vertex that is reachable from every
     * other vertex, and if so, find one.
     * <p>
     * Answer:
     * DAG: Consider an edge (u, v) ∈ E. Since the graph is acyclic, u is not reachable from v.
     * Thus u cannot be the solution to the problem. From this it follows that only a vertex of
     * outdegree zero can be a solution. Furthermore, there has to be exactly one vertex with outdegree zero,
     * or the problem has no solution. This is because if there were multiple vertices with outdegree zero,
     * they wouldn't be reachable from each other.
     * <p>
     * Digraph: Reduce the graph to it's Kernel DAG, then find a vertex of outdegree zero.
     */
    public class Scc {
        private final Digraph g;
        private final Stack<Integer> s = new Stack<>();
        private final boolean marked[];
        private final Digraph r;
        private final int[] scc;
        private final Digraph kernelDag;
    
        public Scc(Digraph g) {
            this.g = g;
            this.r = g.reverse();
            marked = new boolean[g.V()];
            scc = new int[g.V()];
            Arrays.fill(scc, -1);
    
            for (int v = 0; v < r.V(); v++) {
                if (!marked[v]) visit(v);
            }
    
            int i = 0;
            while (!s.isEmpty()) {
                int v = s.pop();
    
                if (scc[v] == -1) visit(v, i++);
            }
            Set<Integer> vPrime = new HashSet<>();
            Set<Map.Entry<Integer, Integer>> ePrime = new HashSet<>();
    
            for (int v = 0; v < scc.length; v++) {
                vPrime.add(scc[v]);
                for (int w : g.adj(v)) {
                    // no self-loops, no parallel edges
                    if (scc[v] != scc[w]) {
                        ePrime.add(new SimpleImmutableEntry<>(scc[v], scc[w]));
                    }
                }
            }
            kernelDag = new Digraph(vPrime.size());
            for (Map.Entry<Integer, Integer> e : ePrime) kernelDag.addEdge(e.getKey(), e.getValue());
        }
    
        public int reachableFromAllOther() {
            for (int v = 0; v < kernelDag.V(); v++) {
                if (kernelDag.outdegree(v) == 0) return v;
            }
            return -1;
        }
    
        // reverse postorder
        private void visit(int v) {
            marked[v] = true;
    
            for (int w : r.adj(v)) {
                if (!marked[w]) visit(w);
            }
            s.push(v);
        }
    
        private void visit(int v, int i) {
            scc[v] = i;
    
            for (int w : g.adj(v)) {
                if (scc[w] == -1) visit(w, i);
            }
        }
    }
    

    在下图中运行它会产生如图所示的强连接组件。简化 DAG 中的顶点 0 可以从其他每个顶点到达。

    我在任何地方都找不到我上面介绍的那种细节。诸如“嗯,这很容易,你这样做,然后你再做其他事情”之类的评论在没有具体细节的情况下被抛出。

    【讨论】:

      猜你喜欢
      • 2021-04-01
      • 2018-02-08
      • 2018-12-22
      • 2021-05-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多