Algorithm 如何将强连接组件减少到一个顶点?

Algorithm 如何将强连接组件减少到一个顶点?,algorithm,graph,directed-graph,strongly-connected-graph,Algorithm,Graph,Directed Graph,Strongly Connected Graph,从 有向图中的可到达顶点。设计一个线性时间算法来确定有向图是否有一个可从 每隔一个顶点 给出了强连接的组件。可以看到这方面的Java代码。如果将每个SCC减少到单个顶点,则可以从其他顶点访问度为零的顶点 问题是,每个人似乎都在谈论减少SCC,但没有提供细节。什么是有效的算法呢?下面是我自己问题的Java解决方案。对于图形表示,它使用来自的edu.princeton.cs:algs4:1.0.3。图压缩似乎有一些通用算法,如中所述;然而,就我的目的而言,以下内容就足够了 /** * 43. &l

  • 有向图中的可到达顶点。设计一个线性时间算法来确定有向图是否有一个可从 每隔一个顶点
  • 给出了强连接的组件。可以看到这方面的Java代码。如果将每个SCC减少到单个顶点,则可以从其他顶点访问度为零的顶点


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

    下面是我自己问题的Java解决方案。对于图形表示,它使用来自的
    edu.princeton.cs:algs4:1.0.3
    。图压缩似乎有一些通用算法,如中所述;然而,就我的目的而言,以下内容就足够了

    /**
     * 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);
            }
        }
    }
    
    /**
    * 43. 可达顶点。
    *
    *DAG:设计一个线性时间算法,以确定DAG是否有一个顶点可以彼此访问
    *顶点,如果是,找到一个。
    *有向图:设计一个线性时间算法来确定一个有向图是否有一个顶点可以从每个节点到达
    *其他顶点,如果是,则找到一个。
    *
    *答复:
    *DAG:考虑边(u,v)∈ 因为这个图是非循环的,所以从v不能到达u。
    *因此,u不可能是问题的解决方案。由此可知,只有
    *零度以上可能是一个解决方案。此外,必须只有一个顶点的度数为零,
    *或者问题没有解决办法。这是因为如果有多个顶点的出度为零,
    *他们彼此联系不到。
    *
    *有向图:将图简化为它的核心DAG,然后找到一个超零度的顶点。
    */
    公共类Scc{
    私有最终有向图g;
    私有最终堆栈s=新堆栈();
    私有最终布尔值标记为[];
    私有最终有向图r;
    专用最终int[]专用条款;
    私有最终有向图kernelDag;
    公共Scc(有向图g){
    这个.g=g;
    这个.r=g.reverse();
    标记=新布尔值[g.V()];
    scc=新的整数[g.V()];
    数组填充(scc,-1);
    对于(int v=0;v
    在下图中运行它将生成强连接组件,如图所示。缩减DAG中的顶点0可以从其他每个顶点访问。


    我在任何地方都找不到我上面介绍的那种细节。诸如“好吧,这很容易,你做了那件事,然后你做了其他事情”之类的评论没有具体细节。假设你已经有了计算SCC的方法和常用的图形、顶点和边方法。然后它只是创建一个新的图,为每个SCC添加一个顶点代表,然后添加边代表

    对于边,您需要能够将原始顶点(边目标)映射到其在新图形中的代表。您可以使用将顶点映射到其SCC的
    Map
    和将SCC映射到其在新图形中的代表顶点的
    Map
    在第一次过程中对其进行建模。或者直接使用
    贴图
    将原始顶点映射到它们的代表


    下面是一个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;
    }
    
    public静态图形graphtoscgraph(图形图形){
    集合sccs=SCCCompution.ComputeSCS(图形);
    Graph sccGraph=新图形();
    Map vertexToScc=newhashmap();
    Map sccToRep=newhashmap();
    //为每个SCC添加一名代表(O(| V |)
    对于(SCC:SCC){
    顶点代表=新顶点();
    sccGraph.addVertex(rep);
    sccToRep.put(scc,代表);
    对于(顶点:scc.getVertex()){
    顶点scc.put(顶点,scc);
    }
    }
    //添加边代表(O(| E |))
    对于(顶点:graph.getVertex()){
    Vertex sourceRep=sccToRep.get(vertexToScc.get(Vertex));
    对于(边:vertex.getOutgoingEdge()){
    顶点析构图=sccToRep.get(vertextscc.get(edge.getDestination());
    Edge edgeRep=新边(sourceRep,destRep);
    如果(!sccGraph.contains(edgeRep)){
    sccGraph.附录(edgeRep);
    }
    }
    }
    返回sccGraph;
    }
    

    时间复杂度在图的大小(顶点和边的数量)上是线性的,因此是最优的。这就是θ(|V |+| E |)


    通常人们使用联合查找(请参阅)数据结构来简化这一过程,并去掉
    映射
    s.

    好吧,基本上,当
    k
    顶点(
    k
    是SCC的数量)和
    m
    边时,您必须构建一个新的图:每个“旧”边在c之间变成一条“新”边