【问题标题】:graph algorithms: reachability from adjacency map图算法:邻接图的可达性
【发布时间】:2011-09-01 20:18:29
【问题描述】:

我有一个依赖图,我表示为 Map<Node, Collection<Node>>(在 Java 中,或 f(Node n) -> Collection[Node] 作为函数;这是从给定节点 n 到依赖于的节点集合的映射n)。该图可能是循环的*。

给定一个节点列表badlist,我想解决一个reachability problem:即生成一个Map<Node, Set<Node>> badmap,它表示从列表badlist中的每个节点N到包括N的一组节点的映射或其他传递依赖它的节点。

例子:

(x -> y means node y depends on node x)
n1 -> n2
n2 -> n3
n3 -> n1
n3 -> n5
n4 -> n2
n4 -> n5
n6 -> n1
n7 -> n1

这可以表示为邻接图{n1: [n2], n2: [n3], n3: [n1, n5], n4: [n2, n5], n6: [n1], n7: [n1]}

如果badlist = [n4, n5, n1],那么我希望得到badmap = {n4: [n4, n2, n3, n1, n5], n5: [n5], n1: [n1, n2, n3, n5]}

我在网上寻找图形算法参考资料时苦苦挣扎,所以如果有人能指出一个有效的算法描述以实现可访问性,我将不胜感激。 (对我没有有帮助的一个例子是http://www.cs.fit.edu/~wds/classes/cse5081/reach/reach.html,因为该算法是确定特定节点A是否可以从特定节点B到达。)

*cyclic: 如果你很好奇,那是因为它代表 C/C++ 类型,并且结构可以具有指向相关结构的指针的成员。

【问题讨论】:

    标签: algorithm graph-algorithm


    【解决方案1】:

    在 Python 中:

    def reachable(graph, badlist):
        badmap = {}
        for root in badlist:
            stack = [root]
            visited = set()
            while stack:
                v = stack.pop()
                if v in visited: continue
                stack.extend(graph[v])
                visited.add(v)
            badmap[root] = visited
        return badmap
    

    【讨论】:

    • 好的,这很简单。出于某种原因,我对您必须重新执行 for root in badlist 循环而不受益于先前执行循环所获得的知识这一事实感到困惑。所以也许可以优化速度......但你所拥有的非常简单。
    • @Jason 这对于有界入度图(即结构引用的不同结构类型的数量)是渐近最优的。如果瓶颈不在其他地方,我会感到惊讶。
    • @Jason:如果您想优化该算法的速度,请将标记位放在顶点本身,而不是在外部 visited 集合中查找。
    【解决方案2】:

    这是我最终使用的,基于@quaint 的回答:

    (为方便起见,需要几个 Guava 类)

    static public <T> Set<T> findDependencies(
            T rootNode, 
            Multimap<T, T> dependencyGraph)
    {
        Set<T> dependencies = Sets.newHashSet();
        LinkedList<T> todo = Lists.newLinkedList();
        for (T node = rootNode; node != null; node = todo.poll())
        {
            if (dependencies.contains(node))
                continue;
            dependencies.add(node);
            Collection<T> directDependencies = 
                    dependencyGraph.get(node);
            if (directDependencies != null)
            todo.addAll(directDependencies);
        }
        return dependencies;
    }
    static public <T> Multimap<T,T> findDependencies(
            Iterable<T> rootNodes, 
            Multimap<T, T> dependencyGraph)
    {
        Multimap<T, T> dependencies = HashMultimap.create();
        for (T rootNode : rootNodes)
            dependencies.putAll(rootNode, 
                    findDependencies(rootNode, dependencyGraph));
        return dependencies;
    }
    static public void testDependencyFinder()
    {
        Multimap<Integer, Integer> dependencyGraph = 
                HashMultimap.create();
        dependencyGraph.put(1, 2);
        dependencyGraph.put(2, 3);
        dependencyGraph.put(3, 1);
        dependencyGraph.put(3, 5);
        dependencyGraph.put(4, 2);
        dependencyGraph.put(4, 5);
        dependencyGraph.put(6, 1);
        dependencyGraph.put(7, 1);
        Multimap<Integer, Integer> dependencies = 
                findDependencies(ImmutableList.of(4, 5, 1), dependencyGraph);
        System.out.println(dependencies);
        // prints {1=[1, 2, 3, 5], 4=[1, 2, 3, 4, 5], 5=[5]}
    }
    

    【讨论】:

      【解决方案3】:

      您也许应该从邻接列表中构建一个可达性矩阵,以便快速搜索。我刚刚找到了Course Notes for CS336: Graph Theory - Jayadev Misra 的论文,它描述了如何从邻接矩阵构建可达性矩阵。

      如果A 是您的邻接矩阵,则可达性矩阵将为R = A + A² + ... + A^n,其中n 是图中的节点数。 A², A³, ... 可以通过以下方式计算:

      • A² = A x A
      • A³ = A x A²
      • ...

      对于矩阵乘法,逻辑或用于代替+逻辑与用于代替x。复杂度为 O(n^4)。

      【讨论】:

      • +1 表示趣味性,但我的 badlist 与图中的节点总数相比是非常少的节点(坏列表的大小通常为 4-10,而节点总数为数万),所以我不确定这对于我的目的是否有效或易于实施。
      【解决方案4】:

      普通的深度优先搜索或广度优先搜索可以解决问题:对每个坏节点执行一次。

      【讨论】:

        【解决方案5】:

        这是一个有效的 Java 解决方案:

        // build the example graph
        Map<Node, Collection<Node>> graph = new HashMap<Node, Collection<Node>>();
        graph.put(n1, Arrays.asList(new Node[] {n2}));
        graph.put(n2, Arrays.asList(new Node[] {n3}));
        graph.put(n3, Arrays.asList(new Node[] {n1, n5}));
        graph.put(n4, Arrays.asList(new Node[] {n2, n5}));
        graph.put(n5, Arrays.asList(new Node[] {}));
        graph.put(n6, Arrays.asList(new Node[] {n1}));
        graph.put(n7, Arrays.asList(new Node[] {n1}));
        
        // compute the badmap
        Node[] badlist = {n4, n5, n1};
        Map<Node, Collection<Node>> badmap = new HashMap<Node, Collection<Node>>();
        
        for(Node bad : badlist) {
            Stack<Node> toExplore = new Stack<Node>();
            toExplore.push(bad);
            Collection<Node> reachable = new HashSet<Node>(toExplore);
            while(toExplore.size() > 0) {
                Node aNode = toExplore.pop();
                for(Node n : graph.get(aNode)) {
                    if(! reachable.contains(n)) {
                        reachable.add(n);
                        toExplore.push(n);
                    }
                }
            }
        
            badmap.put(bad, reachable);
        }
        
        System.out.println(badmap);
        

        【讨论】:

          【解决方案6】:

          就像 Christian Ammer 一样,在执行以下操作时,您将 A 取为邻接矩阵并使用布尔算术,其中 I 是单位矩阵。

              B = A + I;
              C = B * B;
              while (B != C) {
                  B = C;
                  C = B * B;
              }
              return B;
          

          此外,标准矩阵乘法(算术和逻辑)是O(n^3),而不是O(n^2)。但是如果n &lt;= 64,你可以摆脱一个因素n,因为你可以在现在的64位机器上并行处理64位。对于较大的图,64 位并行性也很有用,但着色器技术可能会更好。

          编辑:可以与 SSE 指令并行执行 128 位,而 AVX 则更多。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2021-11-13
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多