【问题标题】:Tarjans algorithm wrongly detecting cyclesTarjans 算法错误地检测循环
【发布时间】:2016-11-21 14:39:38
【问题描述】:

每当我在任何图上运行 tarjans 算法时,它总是声称有一个循环,例如这个图:

A -> B -> C

算法会告诉我有一个循环:

[a]
[b]

当有个循环时,例如:

A -> B -> C -> A

输出很奇怪:

[c, b, a]
[a]
[b]

这是我的实现:

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.stream.Collectors;

public class Tarjans {

    private static class Node {
        public int index = -1, lowLink = -1;
        public String name;

        public Node(String name) {
            this.name = name;
        }

        public String toString() {
            return name;
        }
    }

    HashMap<String, Node> nodes = new HashMap<>();
    HashMap<String, ArrayList<Node>> graph = new HashMap<>();

    private int index = 0;
    private ArrayDeque<Node> visited = new ArrayDeque<>();
    private HashSet<String> stack = new HashSet<>();

    public ArrayList<ArrayList<Node>> tarjan() {
        ArrayList<ArrayList<Node>> cycles = new ArrayList<>();
        for (String key : graph.keySet()) {
            Node n = nodes.get(key);
            if (n == null) {
                System.err.println("what is " + n + "?");
                return new ArrayList<ArrayList<Node>>();
            }

            ArrayList<Node> cycle = strongConnect(n);
            if (cycle.size() > 0) {
                cycles.add(cycle);
            }
        }
        return cycles;
    }

    private ArrayList<Node> strongConnect(Node node) {
        node.index = index;
        node.lowLink = index;
        index += 1;

        visited.push(node);
        stack.add(node.name);

        ArrayList<Node> neighbours = graph.get(node.name);
        if (neighbours == null) return new ArrayList<>();

        neighbours.forEach(n -> {
            if (n.index == -1) {
                strongConnect(n);
                node.lowLink = Math.min(node.lowLink, n.lowLink);
            }
            else if (stack.contains(n.name)) {
                node.lowLink = Math.min(node.lowLink, n.index);
            }
        });

        ArrayList<Node> cycle = new ArrayList<>();
        if (node.lowLink == node.index) {
            Node p = null;
            do {
                p = visited.pop();
                stack.remove(p.name);
                cycle.add(p);
            } while (p != node);
        }
        return cycle;
    }

    private void foo() {
        nodes.put("a", new Node("a"));
        nodes.put("b", new Node("b"));
        nodes.put("c", new Node("c"));

        // A -> B -> C -> A
        graph.put("a", new ArrayList<>(Arrays.asList(nodes.get("b"))));
        graph.put("b", new ArrayList<>(Arrays.asList(nodes.get("c"))));
        graph.put("c", new ArrayList<>(Arrays.asList(nodes.get("a"))));

        ArrayList<ArrayList<Node>> cycles = tarjan();
        for (ArrayList<Node> cycle : cycles) {
            System.out.println("[" + cycle.stream().map(Node::toString).collect(Collectors.joining(",")) + "]");
        }
    }

    public static void main(String[] args) {
        new Tarjans().foo();
    }

}

但我不确定我哪里出错了。我已经按照近 1:1 的比例关注了关于 tarjans 算法的维基百科文章和伪代码。我对图论和图​​算法很陌生,所以我无法理解这里的错误是什么。

修复 tarjan()

public ArrayList<ArrayList<Node>> tarjan() {
    ArrayList<ArrayList<Node>> cycles = new ArrayList<>();
    for (Node n : nodes.values()) {
        if (n == null) {
            System.err.println("what is " + n + "?");
            return new ArrayList<ArrayList<Node>>();
        }

        if (n.index == -1) {
            ArrayList<Node> cycle = strongConnect(n);
            if (cycle.size() > 0) {
                cycles.add(cycle);
            }   
        }
    }
    return cycles;
}

【问题讨论】:

  • (维基百科以 Tarjan 的名义保留了多种算法,Strongly Connected Components 算法不是“默认”算法。)如修订版 1 中所述,代码包含语法错误(并且缺少 Node)。经过一番清理,我没有得到 一个 循环 (a, b),而是两个: ((a), (b)) - if I不要将(c, emptyList()) 放入图中。如果我将(a,b)和(b,a)都放入图中,我确实得到((a,b),...),“...”混乱('index'的处理在以后/递归调用中?)。
  • @greybeard 我很困惑,你是什么意思?
  • 呃整个事情。你想说什么?你清理代码了吗?是维基百科修订版中的代码吗?最后一点是一个问题(?),但我不明白你在问什么。
  • 我不明白你如何处理它。键集。你在这里是什么意思,你能详细说明一下吗?另外,“(几个小时外)”是什么意思?
  • 方法tarjan() 仍然看起来关闭:查看以for ( 开头的行的末尾,以及以'}' 开头的对齐行的末尾(尝试从这篇文章中复制代码&将其插入您的开发环境中)。将 A、B、C 输入为一个圆圈,我得到 [[Node&lt;C&gt;, Node&lt;B&gt;, Node&lt;A&gt;], [Node&lt;B&gt;], [Node&lt;C&gt;]]:可能是您开始包含所有代码(&输入)以重现您的发现的时候了 - Minimal, Complete, and Verifiable Example

标签: java algorithm graph tarjans-algorithm


【解决方案1】:

从问题中提供的代码的第一个修订版开始,问题归结为 nearly 不够接近:I've followed the wikipedia article on [Tarjan's Strongly Connected Components] algorithm nearly 1:1 and the pseudocode
(并且可能命名(要保存的变量)强连接组件 cycle:如果边 (a, b), (a, c), (b, a), (b, c) 和 ( c、a) 属于一个图,顶点/节点 a、b 和 c 在一个强连通分量中,它既不是环也不是恰好(成对)共享顶点的环。) p>

已经为已访问的节点调用了strongConnect() - 在修订版 7 中已修复。
在修订版 7 中,当节点没有邻居/继任者时,仍然检查节点是否符合强连接组件的资格。
处理 强连接组件 发现后并不容易:将 Set&lt;Set&lt;Node&gt;&gt; 作为“算法(实例)”的数据成员添加到其中。

一旦你的实现工作正常并且代码被清理和注释,我建议在CODE REVIEW 上展示它:有很多机会让每个人的生活(作为 (Java) 编码器)更简单,从你的开始。

【讨论】:

  • 万事开头难。
  • 我不确定您所说的“将 Set> 作为数据成员”是什么意思。我要添加什么?什么样的集合,哈希集合?感谢代码审查的想法,我从来不知道这个小子社区存在! :-)
  • mean by "have a Set&lt;Set&lt;Node&gt;&gt; as a data member: class Tarjan { …Set&lt;Set&lt;Node&gt;&gt; scComponents = new HashSet&lt;&gt;();…} What do I add to this?: scComponents.add(stronglyConnectedComponent) What kind of set, a hash set?应该没关系,功能上:如果你想要没有(可能)检查重复项,Collection 就可以了。
  • 我看不出这与将它们添加到数组列表并从 tarjan() 方法返回它们有何不同
  • 尝试返回多个连接的组件,从递归调用到strongConnect()
猜你喜欢
  • 2011-09-17
  • 2012-06-18
  • 2011-11-15
  • 2012-06-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多