Почему я получаю StackOverFlowError при попытке DFS этого графа?

Я пытаюсь написать алгоритм, который определяет, связан ли граф или нет. Я думаю, что мой код почти правильный, хотя я продолжаю получать 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) для посещенного массива.

ОБНОВЛЕНИЕ 2:

Вот как выглядит график: введите здесь описание изображения

Я уверен, что проблема после N3, алгоритм зацикливается после N3, потому что он не понимает, что N1 уже был посещен. Я действительно не понимаю, почему это происходит!

ОБНОВЛЕНИЕ 3

Строки имеют разные имена и дубликатов нет.. например, indexOf(nodes.get(2)) дает 2, а не 0 или что-то еще..

Проблема в том, что после посещения N3 алгоритм должен остановиться и вернуть 0 или 1, но я не знаю, как это сделать :)


person Loolooii    schedule 20.02.2011    source источник
comment
Ха-ха! Вы сказали StackOverflow!   -  person dave    schedule 20.02.2011
comment
Прочитав ваш код 10 раз, я начинаю подозревать listOfChildren. В любом случае, с графом из 5 узлов это займет 10 минут, чтобы отследить его с помощью отладчика, карандаша и листа бумаги... Используйте F5 :)   -  person Calvin1602    schedule 20.02.2011
comment
У вас есть дубликаты? Узел с тем же s ?   -  person Calvin1602    schedule 20.02.2011
comment
Вы делаете это очень сложно, используя String в качестве узла. Почему бы не сделать класс Node?   -  person Ishtar    schedule 20.02.2011
comment
Вы должны добавить логирование в свой код. Я бы также посоветовал вам использовать синтаксис for each, чтобы сделать вещи немного чище. А также вы должны предоставить свое определение узлов и indexOf.   -  person Michael Aaron Safyan    schedule 20.02.2011
comment
Кроме того, почему вы используете int вместо boolean для isConnected()?   -  person Michael Aaron Safyan    schedule 20.02.2011
comment
@ Calvin1602 нет, s - это узел, с которого алгоритм начинается каждый раз, когда запускается isConnected () .. Он не дублируется. узлы не называю..   -  person Loolooii    schedule 20.02.2011
comment
@Майкл Аарон Сафьян, спасибо за совет, и в чем разница между int и boolean в этом случае? Моя проблема - StackOverFlow :)   -  person Loolooii    schedule 20.02.2011
comment
Насколько велик ваш график?   -  person rlibby    schedule 20.02.2011
comment
всего 5 узлов! он направлен и содержит цикл где-то в графе.   -  person Loolooii    schedule 20.02.2011
comment
Возможно ли, что StackOverflowError на самом деле бросается в listOfChildren? Если нет, то нужно вставить System.err.println(s); в начало метода и исследовать первый повтор.   -  person rlibby    schedule 20.02.2011
comment
@rlibby Когда я помещаю System.out.println(ind); непосредственно перед оператором if в цикле for я вижу, что алгоритм фактически застревает в цикле. Он просто печатает 1 2 3 ... 1 2 3 .. Итак, проблема теперь в цикле на графике. Я добавлю изображение графика к вопросу   -  person Loolooii    schedule 20.02.2011
comment
Что ж, это говорит вам о том, что вы неоднократно посещаете узел, у которого есть дочерние элементы с индексами 1, 2 и 3. Если вы сделаете то, что я предложил, вы получите имя этого узла. Если проблема в том, что он печатает вечно, просто напечатайте, если счетчик ‹ 10 или что-то в этом роде, проблема должна появиться раньше.   -  person rlibby    schedule 20.02.2011
comment
@rlibby Я только что сделал это, он печатает: N3, N1, N2 ... N3, N1, N2 и никогда не останавливается, пока не произойдет исключение. Теперь я уверен, что алгоритм посещает N1 после N3 снова и снова. Как я могу остановить это? Благодарю.   -  person Loolooii    schedule 20.02.2011
comment
Возможно ли, что listOfChildren очищает ваш массив visited, возможно, вызывая setUnvisited()?   -  person rlibby    schedule 20.02.2011
comment
@rlibby.. ВАУ! Ты замечательный! Пожалуйста, отправьте это как ответ, чтобы я мог принять ваш ответ :)   -  person Loolooii    schedule 20.02.2011
comment
@rlibby ну, это было уродливо ^^ мило! Вы заслуживаете +1.   -  person Calvin1602    schedule 01.03.2011


Ответы (3)


Причина, по которой вам было так трудно отследить это, заключается в том, что все изменяемое состояние в вашем классе графа. В частности, у вас есть count и visited, причем последний изменяется обоими вашими методами isConnected и setUnvisited.

Вы зависите от своего массива 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;
}

Поскольку visited и counter больше не используются совместно, ваша ошибка исчезла. Это также устраняет другую ошибку, которая у вас была (но пока не замечена), когда работает только первый вызов isConnected(), потому что в этом случае вы не выполняли сброс 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);
        }
    }
}

На самом деле я не пытался компилировать или запускать это, так что могут быть ошибки, но вы поняли, я надеюсь.

person rlibby    schedule 20.02.2011

Я сделал небольшую тестовую программу на основе ваших обновлений, и, похоже, она работает как шарм:

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) проверяет, когда они должны совпадать?).

person esaj    schedule 20.02.2011

Настоящая проблема в том, что вы пытаетесь представить узел строкой. Делая это, вы должны хранить дочерние элементы узла где-то еще, listOfChildren. Вам также необходимо отслеживать, кого вы посещали, boolean[] visited.

Узел теперь идентифицируется двумя способами

  1. listOfChildren использует строковое представление "node1","node2",...
  2. посещенный [] использует индекс, индекс в nodes.

Ого. Теперь вы должны быть уверены, что каждое строковое представление имеет ровно один индекс в массиве узлов. (Должно быть взаимно однозначное отображение двух идентификаций.)

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

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

Видите проблему? Есть один 'node' с тремя индексами, или есть три узла с одинаковыми именами!

Но я использую массив, чтобы увидеть, был ли уже посещен узел!

Может быть, это не тот же «узел», вы имеете в виду «узел» String или «узел» индекса?

Чтобы исправить это, создайте одну идентификацию, одно представление для узла:

public class Node
{
  List<Node> children;
}

Строка или индекс не нужны! Просто пусть node будет Node.

person Ishtar    schedule 20.02.2011
comment
Я не могу представить, что это решит проблему. В приведенном выше коде дубликаты просто игнорируются. Один и тот же indexOf используется для поиска текущего индекса и дочернего индекса. Поэтому он всегда относится к первому индексу. Поэтому любые дубликаты просто никогда не посещаются. Это не вызывает бесконечную рекурсию, просто неправильный ответ для связанных графов с дубликатами. - person rlibby; 20.02.2011
comment
каждая строка имеет ровно один индекс в списке узлов arrayList. Я не думаю, что проблема, потому что узлы имеют разные имена. Например: N0, N1,.....,и т.д. Итак, indexOf(nodes.get(2)) дает 2.. - person Loolooii; 20.02.2011
comment
@rlibby - Правда, я не имел в виду, что это решит проблему напрямую. Но наличие хорошего дизайна облегчает отслеживание ошибок. Кроме того, поскольку OP хочет правильный код, мне просто нужно было показать эту проблему. - person Ishtar; 20.02.2011