为什么在尝试DFS此图时会出现StackOverFlowError?

时间:2011-02-20 10:31:57

标签: java graph depth-first-search stack-overflow

我正在尝试编写一个算法来确定图表是否已连接。我认为我的代码几乎是正确的,尽管我一直得到StackOverFlowError。我个人认为,因为图中有一个循环,我正在测试我的算法,我的代码不理解它并且循环。但我正在使用数组来查看是否已访问过某个节点!所以这不应该发生!无论如何这是我的代码:

public int isConnected(String s) 
    {

        int in = nodes.indexOf(s);


        visited[in] = true;
        counter++;
        for(int i = 0; i < listOfChildren(s).size(); i++)
        {
            int ind = nodes.indexOf(listOfChildren(s).get(i));
            if(visited[ind] == false)
            {
                isConnected(listOfChildren(s).get(i));
            }

        }
        System.out.println(counter);
        if(counter == nodes.size())
            return 1;
        return 0;

    }

s是我开始使用的节点,节点是节点的ArrayList,并且与访问的数组具有相同的大小(在本例中为5)。在访问开始时看起来像这样:[false false false false false],因此没有访问任何节点。 listOfChildren()返回特定节点的子节点的ArrayList(不是所有子节点,只是与节点相邻的子节点)。我确信listOfChildren()有效,因为我测试了43545454次。

感谢任何帮助(如果可能,最少更改代码)。感谢。

更新

我的图表是针对..

我定义了这样的访问:

private boolean[] visited;

我在我的构造函数中将其中的所有内容设置为false:

public void setUnvisited()
    {
        visited =  new boolean[nodes.size()];

        for(int i = 0; i < nodes.size(); i++)
        {
            visited[i] = false;
        }
    }

节点是字符串!访问过,节点长度相同。这就是为什么我可以对访问过的数组使用nodes.indexOf(blabla)。

UPDATE2:

这是图表的样子: enter image description here

我确定问题出在N3之后,算法在N3之后进入循环,因为它不知道已经访问过N1。我真的不明白为什么会这样!

UPDATE3

字符串有不同的名称,没有重复..所以例如indexOf(nodes.get(2))给出2而不是0或其他任何东西..

问题是,在访问N3后,算法应该停止并返回0或1,但我不知道该怎么做:)

3 个答案:

答案 0 :(得分:2)

您发现这么难以追踪的原因是图表类中的所有可变状态。特别是您有countvisited,后者会被您的方法isConnectedsetUnvisited修改。

您依赖于数组visited来告诉您何时停止递归,但是通过调用listOfChildren会在每次递归调用时意外重置数组。

解决此问题的一种方法是使visited不是类成员。这是一个很少修改代码的解决方案:

public boolean isConnected(String s) {
    int nVisited = isConnected(s, new boolean[nodes.size()], 0);
    return nVisited == nodes.size();
}

private int isConnected(String s, boolean[] visited, int counter) 
{

    int in = nodes.indexOf(s);


    visited[in] = true;
    counter++;
    for(int i = 0; i < listOfChildren(s).size(); i++)
    {
        int ind = nodes.indexOf(listOfChildren(s).get(i));
        if(visited[ind] == false)
        {
            counter = isConnected(listOfChildren(s).get(i), visited, counter);
        }

    }
    System.out.println(counter);
    return counter;
}

由于不再共享visitedcounter,因此您遇到的错误已经消失。这也解决了你所遇到的另一个错误(但尚未注意到)只有第一个isConnected()调用有效 - 因为在这种情况下你重置visitedcounter恰当。

与上述相同构思的更清晰的实现:

public boolean isConnected(String s) {
    Set<String> visited = new HashSet<String>();
    isConnected(s, visited);
    return visited.size() == nodes.size();
}

private void isConnected(String s, Set<String> visited) 
{
    visited.add(s);
    for (String child : listOfChildren(s)) {
        if (!visited.contains(s)) {
            isConnected(child, visited);
        }
    }
}

我实际上并没有尝试编译或运行它,因此可能存在错误,但我希望你能得到这个想法。

答案 1 :(得分:1)

我根据您的更新制作了一个小型测试程序,它看起来像一个魅力:

public class NodeTest
{
    static ArrayList<String> nodes = new ArrayList<String>();
    boolean visited[] = {false, false, false, false, false};

    int counter = 0;

    static HashMap<String, ArrayList<String>> childMap = new HashMap<String, ArrayList<String>>();

    static
    {
        nodes.add("N0");
        nodes.add("N1");
        nodes.add("N2");
        nodes.add("N3");
        nodes.add("N4");

        //N4 --> N2 --> N3 --> N1 <-- N0
        //       ^-------------+
        ArrayList<String> list = new ArrayList<String>();
        list.add("N2");
        childMap.put("N4", list); //N4 to N2

        list = new ArrayList<String>();
        list.add("N3"); 
        childMap.put("N2", list); //N2 to N3

        list = new ArrayList<String>();
        list.add("N1");
        childMap.put("N3", list); //N3 to N1

        list = new ArrayList<String>();
        list.add("N2");
        childMap.put("N1", list); //N1 to N2

        list = new ArrayList<String>();
        list.add("N1");
        childMap.put("N0", list); //N0 to N1
    }

    @Test
    public void test()
    {
        System.out.println("Is connected = " + isConnected("N0"));
    }

    public int isConnected(String s) 
    {
        System.out.println("Handling node " + s);

        int in = nodes.indexOf(s);


        visited[in] = true;
        counter++;
        for(int i = 0; i < listOfChildren(s).size(); i++)
        {
            int ind = nodes.indexOf(listOfChildren(s).get(i));
            if(visited[ind] == false)
            {
                System.out.println("Recursing into child " + listOfChildren(s).get(i));
                isConnected(listOfChildren(s).get(i));
            }
            else
            {
                System.out.println("Node " + listOfChildren(s).get(i) + " has already been visited");
            }

        }
        //System.out.println(counter);
        if(counter == nodes.size())
            return 1;
        return 0;

    }

    public ArrayList<String> listOfChildren(String s)
    {
        return childMap.get(s);
    }

}

isConnected-method与你的相同,我只是添加了一些用于记录的消息。输出:

Handling node N0
Recursing into child N1
Handling node N1
Recursing into child N2
Handling node N2
Recursing into child N3
Handling node N3
Node N1 has already been visited
Is connected = 0

正如预期的那样,图表未连接(它与您提出的问题相同)。如果我将起始节点更改为N4并将N1的唯一子节点更改为N0,则算法会将图形正确识别为已连接。基于此,我会说问题是 listOfChildren 返回一些古怪的东西或者访问所使用的索引(在某些时候, visited [in] =如果(visit [ind] == false)正在检查它们应该相同的时间,那么会将某些其他索引标记为所访问的。)

答案 2 :(得分:0)

真正的问题是,您正在尝试通过String表示节点。通过这样做,您必须将节点的子节点存储在其他位置listOfChildren。您还需要跟踪您访问过的人boolean[] visited

现在以两种方式识别节点

  1. listOfChildren使用字符串表示形式"node1","node2",...
  2. visited []使用索引,nodes中的索引。
  3. 大穗。现在,您必须确保每个String表示在nodes数组中只有一个索引。 (必须对这两种标识进行一对一的映射。)

    nodes.add("node");
    nodes.add("node");
    nodes.add("node");
    
    return nodes.indexOf( nodes.get(2) );//what does this return? 0!
    

    看到问题?有一个'node'有三个索引,或者有三个节点具有相同的名称!

      

    但我正在使用数组来查看是否已访问过某个节点!

    也许这不是同一个'node',你的意思是String'节点',还是索引'node'?

    要解决此问题,请为节点创建一个标识,一个表示形式:

    public class Node
    {
      List<Node> children;
    }
    

    不需要字符串或索引!只需让节点成为节点。