簡體   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