为什么在尝试对该图进行 DFS 时会出现 StackOverFlowError?

发布于 2024-10-18 06:29:18 字数 1746 浏览 1 评论 0原文

我正在尝试编写一种算法来确定图是否连通。我认为我的代码几乎是正确的,尽管我不断收到 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)来访问数组。

更新2:

图表如下所示: 在此处输入图像描述

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

UPDATE3

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

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

I am trying to write an algorithm that determines whether a graph is connected or not. I think my code is almost correct, although I keep getting StackOverFlowError. I personally think because there's a cycle in the graph I'm testing my algorithm with, my code doesn't understand that and comes in a loop. But I'm using an array to see if a node was already visited! So that should not happen! Anyways this is my code:

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 is some node I begin with, nodes is an ArrayList of nodes and has the same size(5 in this case) as the array visited. In the beginning visited looks like this: [false false false false false], so none of the nodes was visited. listOfChildren() return an ArrayList of the children(not all of them, just the ones adjacent to the node) of a particular node. I am sure that listOfChildren() works, since I tested it 43545454 times.

Any help is appreciated(with minimum change of the code, if possible). Thanks.

UPDATE:

My graph is directed..

I define visited like this:

private boolean[] visited;

and I set everything in it to false in my constructor this code:

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

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

The nodes are strings! visited and nodes have the same length. That's why I can use nodes.indexOf(blabla) for the array visited.

UPDATE2:

This is how the graph looks like:
enter image description here

I'm sure the problem is after N3, the algorithm goes in a loop after N3, because it doesn't understand that N1 was already visited. I really don't understand why this happens!

UPDATE3

String have different names and there are no duplicates.. so for example indexOf(nodes.get(2)) gives 2 and not 0 or anything else..

The problem is that after visiting N3 the algorithm should stop and return 0 or 1, but I don't know how to do that :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

难如初 2024-10-25 06:29:18

您发现这很难追踪的原因是您的图形类中的所有可变状态。特别是,您有 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() 调用有效 - 因为在这种情况下您没有重置 < code>visited 或 counter 适当地。

与上面相同的想法的更清晰的实现:

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);
        }
    }
}

我实际上没有尝试编译或运行它,所以可能存在错误,但我希望你明白这个想法。

The reason you found this so difficult to track down is all the mutable state in your graph class. In particular you have count and visited, the latter being modified by both your methods isConnected and setUnvisited.

You depend on your array visited to tell you when to stop recursing, but accidentally reset the array on every recursive call through your call to listOfChildren.

One way around this to make it so that visited is not a class member. Here's a solution which modifies your code very little:

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;
}

Since visited and counter are no longer shared, the bug you had is gone. This also solves another bug you had (but didn't notice yet) where only the first isConnected() call works--because in that case you weren't resetting visited or counter appropriately.

A cleaner implementation of the same idea as above:

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);
        }
    }
}

I haven't actually tried to compile or run that, so there may be bugs, but you get the idea, I hope.

纸短情长 2024-10-25 06:29:18

我根据您的更新制作了一个小测试程序,它似乎很有魅力:

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 返回一些古怪的东西,要么是 visited 使用的索引(在某些时候,visited[in] = true 标记其他一些索引已被访问,而不是 if(visited[ind] == false) 检查它们何时应该相同?)。

I made a small test program based on your updates, and it seems to work like a charm:

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);
    }

}

The isConnected-method is same as yours, I just added some messages for logging. Output:

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

As expected, the graph is not connected (it's the same graph you drew on your question). If I change the starting node to N4 and change N1's only child to N0, the algorithm correctly recognizes the graph as connected. Based on this, I'd say the problem is either the listOfChildren returning something wacky or the indices used with visited (at some point, visited[in] = true marks some other index as visited than if(visited[ind] == false) is checking against when they should be the same?).

酒几许 2024-10-25 06:29:18

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

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

  1. listOfChildren 使用字符串表示形式 "node1","node2",...
  2. visited[] 使用索引,即 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;
}

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

The real problem is, you are trying to represent a Node by a String. By doing that, you have to store the children of a Node somewhere else, listOfChildren. You also need to keep track of who you visited, boolean[] visited.

A node is now identified in two ways

  1. listOfChildren uses the String representation "node1","node2",...
  2. visited[] uses an index, the index in nodes.

Oho. Now, you must be sure, that every String representation has exactly one index in the nodes array. (There must be a one-to-one mapping of the two identifications.)

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

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

See the problem? There is one 'node' with three indices, or there are three nodes with the same name!

But I'm using an array to see if a node was already visited!

Maybe that is not the same 'node', do you mean the String 'node', or the index 'node'?

To fix this make one identification, one representation, for a node:

public class Node
{
  List<Node> children;
}

No String, or index needed! Just let node be a Node.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文