繁体   English   中英

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

[英]Why do I get StackOverFlowError when trying to DFS this graph?

我正在尝试编写一种算法来确定是否连接了图形。 我认为我的代码几乎是正确的,尽管我不断收到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是我开始的某个节点,nodes是节点的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:

图是这样的: 在此处输入图片说明

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

UPDATE3

字符串具有不同的名称,并且没有重复..因此,例如indexOf(nodes.get(2))给出2而不是0或其他。

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

您发现很难追踪的原因是图形类中的所有可变状态。 特别是您已经count并且visited ,后者由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);
        }
    }
}

我实际上并未尝试编译或运行该程序,因此可能存在错误,但是我希望您能理解。

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

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方法与您的方法相同,我只是添加了一些用于记录的消息。 输出:

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或指数(在某些时候,如走访了如果 参观[中] =真正的标记其他一些指数(参观[IND] ==假)正在检查它们何时应该相同?)。

真正的问题是,您试图用字符串表示Node。 这样,您必须将Node的孩子存储在其他位置listOfChildren 您还需要跟踪访问过的人boolean[] visited

现在可以通过两种方式识别节点

  1. listOfChildren使用字符串表示形式"node1","node2",...
  2. visit []使用索引, nodes的索引。

大穗。 现在,您必须确保,每个String表示形式在nodes数组中都只有一个索引。 (两个标识必须一一对应。)

nodes.add("node");
nodes.add("node");
nodes.add("node");

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

看到问题了吗? 有一个带有三个索引的'node' ,或者有三个同名节点!

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

也许这不是相同的“节点”,您是指字符串“节点”还是索引“节点”?

要解决此问题,请为节点做一个标识( 一种表示):

public class Node
{
  List<Node> children;
}

无需字符串或索引! 只要让节点成为一个节点即可。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM