[英]Why do I get StackOverFlowError when trying to DFS this graph?
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.
我认为我的代码几乎是正确的,尽管我不断收到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. s是我开始的某个节点,nodes是节点的ArrayList,并且具有与访问的数组相同的大小(在本例中为5)。 In the beginning visited looks like this: [false false false false false], so none of the nodes was visited.
在开始访问时,看起来像这样:[false false false false false],因此没有节点被访问过。 listOfChildren() return an ArrayList of the children(not all of them, just the ones adjacent to the node) of a particular node.
listOfChildren()返回特定节点的子节点(不是所有子节点,而是所有与节点相邻的子节点)的ArrayList。 I am sure that listOfChildren() works, since I tested it 43545454 times.
我确定listOfChildren()可以正常工作,因为我对其进行了43545454次测试。
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: 然后在我的构造函数中将此代码中的所有内容设置为false:
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.
这就是为什么我可以将nodes.indexOf(blabla)用于访问的数组。
UPDATE2: UPDATE2:
This is how the graph looks like: 图是这样的:
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. 我确定问题出在N3之后,该算法在N3之后进入循环,因为它不了解N1已经被访问过。 I really don't understand why this happens!
我真的不明白为什么会这样!
UPDATE3 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.. 字符串具有不同的名称,并且没有重复..因此,例如indexOf(nodes.get(2))给出2而不是0或其他。
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 :) 问题是在访问N3之后,算法应停止并返回0或1,但我不知道该怎么做:)
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
. 特别是您已经
count
并且visited
,后者由isConnected
和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
. 您依赖于
visited
的数组来告诉您何时停止递归,但是通过对listOfChildren
的调用,在每次递归调用上意外重置了数组。
One way around this to make it so that visited
is not a class member. 解决这个问题的一种方法是使
visited
不是班级成员。 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. 由于
visited
和counter
不再共享,因此您所遇到的错误已消失。 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. 这也解决了另一个错误(但尚未引起注意),在该错误中只有第一个
isConnected()
调用有效-因为在这种情况下,您没有适当地重置visited
或counter
。
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. 我实际上并未尝试编译或运行该程序,因此可能存在错误,但是我希望您能理解。
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. isConnected方法与您的方法相同,我只是添加了一些用于记录的消息。 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.
如果我将起始节点更改为N4并将N1的唯一子节点更改为N0,则该算法会正确地将图识别为已连接。 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?).
基于此,我想说的问题是,要么访问使用返回的东西古怪的listOfChildren或指数(在某些时候,如走访了如果 参观[中] =真正的标记其他一些指数(参观[IND] ==假)正在检查它们何时应该相同?)。
The real problem is, you are trying to represent a Node by a String. 真正的问题是,您试图用字符串表示Node。 By doing that, you have to store the children of a Node somewhere else,
listOfChildren
. 这样,您必须将Node的孩子存储在其他位置
listOfChildren
。 You also need to keep track of who you visited, boolean[] visited
. 您还需要跟踪访问过的人
boolean[] visited
。
A node is now identified in two ways 现在可以通过两种方式识别节点
"node1","node2",...
"node1","node2",...
nodes
. nodes
的索引。 Oho. 大穗。 Now, you must be sure, that every String representation has exactly one index in the nodes array.
现在,您必须确保,每个String表示形式在nodes数组中都只有一个索引。 (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! 有一个带有三个索引的
'node'
,或者有三个同名节点!
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.
只要让节点成为一个节点即可。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.