【问题标题】:How to perform different basic traversals of graphs?如何对图进行不同的基本遍历?
【发布时间】:2020-04-01 22:48:36
【问题描述】:

我正在尝试对给定图执行迭代广度优先遍历、迭代深度优先遍历和递归深度优先遍历(使用邻接矩阵)。

在当前状态下,我的程序会输出各种错误答案。

这里有一些例子。

我期待

From Node A
DFS (iterative): A B H C D E I F G
DFS (recursive): A B H C D E I F G
BFS (iterative): A B D I H C E F G

但我却得到了

From Node A
DFS (iterative): A I D B H C F G E 
DFS (recursive): A B H C F D E I G 
BFS (iterative): A B D I H C E F G  

我不确定我的程序的问题在于遍历的实现,还是我对程序其他部分的实现。更具体地说,我不确定我的实现 connectNode 或 getNeighbors 方法是导致错误输出的原因,还是我的遍历实现。

编辑:如果这很重要,应该按升序选择邻居。也许这是问题的一部分?

EDIT2:感谢@HylianPikachu 的建议,我添加了新的代码行。我现在得到了完整的答案,但它们的顺序仍然不正确。

EDIT3:我添加了代码,以便检查根节点是否已访问 bfs 和递归 dfs。我认为。我还应该注意,我得到了这段代码的一部分,并被告知要填写其余部分。我被告知要使用堆栈和队列,即使可能有更好的选择。

EDIT4:添加了建议的内容,现在,迭代 BFS 可以工作并获得正确的结果。但是,两个 DSF 搜索仍然不起作用。我修改了上面程序的结果,以显示这一点。

import java.util.*;

public class GraphM {
    public Node rootNode;
    public List<Node> nodes = new ArrayList<Node>(); // nodes in graph
    public int[][] adjMatrix; // adjacency Matrix

    public void setRootNode(Node n) {
        rootNode = n;
    }

    public Node getRootNode() {
        return rootNode;
    }

    public void addNode(Node n) {
        nodes.add(n);
    }

    // This method connects two nodes
    public void connectNode(Node src, Node dst) {

        if(adjMatrix == null) {
            adjMatrix = new int[nodes.size()][nodes.size()];
        }

        adjMatrix[nodes.indexOf(src)][nodes.indexOf(dst)] = 1;
        adjMatrix[nodes.indexOf(dst)][nodes.indexOf(src)] = 1;

    }

    // Helper method to get one unvisited node from a given node n.
    private Node getUnvisitedChildNode(Node n) {
        int index = nodes.indexOf(n);
        int size = adjMatrix.length;
        for (int j = 0; j < size; j++)
            if (adjMatrix[index][j] == 1 && ((Node) nodes.get(j)).visited == false)
                return nodes.get(j);
        return null;
    }

    // get all neighboring nodes of node n.
    public List<Node> getNeighbors(Node n) {
        List<Node> neighbors = new ArrayList<Node>();

        for(int i = 0; i < nodes.size(); i ++) {
            if (adjMatrix[nodes.indexOf(n)][i] == 1) {
                neighbors.add(nodes.get(i));
            }
           Collections.sort(neighbors);
        }
        return neighbors;

    }

    // Helper methods for clearing visited property of node
    private void reset() {
        for (Node n : nodes)
            n.visited = false;
    }

    // Helper methods for printing the node label
    private void printNode(Node n) {
        System.out.print(n.label + " ");
    }

    // BFS traversal (iterative version)
    public void bfs() {


        Queue<Node> queue = new LinkedList<Node>();

        queue.add(rootNode);

        while(!queue.isEmpty()) {

            Node node = queue.poll();
            printNode(node);
            node.visited = true;

            List<Node> neighbors = getNeighbors(node); 

            for ( int i = 0; i < neighbors.size(); i ++) {
                Node n = neighbors.get(i);

                if (n != null && n.visited != true) {
                    queue.add(n);
                    n.visited = true;
                }
            }

        }

    }

    // DFS traversal (iterative version)
    public void dfs() {

        Stack<Node> stack = new Stack<Node>();
        stack.add(rootNode);
        while(!stack.isEmpty()){
            Node node = stack.pop();

            if(node.visited != true) {
                printNode(node);
                node.visited = true;
            }

            List<Node> neighbors = getNeighbors(node);
            for (int i = 0; i < neighbors.size(); i++) {
                Node n = neighbors.get(i);
                if(n != null && n.visited != true) {
                    stack.add(n);
                }
            }

        }

    }

    // DFS traversal (recursive version)
    public void dfs(Node n) {

        printNode(n);
        n.visited = true;
        List<Node> neighbors = getNeighbors(n);
        for (int i = 0; i < neighbors.size(); i ++) {
            Node node = neighbors.get(i);
            if(node != null && node.visited != true) {
                dfs(node);
            }
        }

    }

    // A simple Node class
    static class Node implements Comparable<Node> {
        public char label;
        public boolean visited = false;

        public Node(char label) {
            this.label = label;
        }

        public int compareTo(Node node) {
            return Character.compare(this.label,  node.label);
        }
    }


    // Test everything
    public static void main(String[] args) {

        Node n0 = new Node('A');
        Node n1 = new Node('B');
        Node n2 = new Node('C');
        Node n3 = new Node('D');
        Node n4 = new Node('E');
        Node n5 = new Node('F');
        Node n6 = new Node('G');
        Node n7 = new Node('H');
        Node n8 = new Node('I');

        // Create the graph (by adding nodes and edges between nodes)
        GraphM g = new GraphM();
        g.addNode(n0);
        g.addNode(n1);
        g.addNode(n2);
        g.addNode(n3);
        g.addNode(n4);
        g.addNode(n5);
        g.addNode(n6);
        g.addNode(n7);
        g.addNode(n8);

        g.connectNode(n0, n1);
        g.connectNode(n0, n3);
        g.connectNode(n0, n8);
        g.connectNode(n1, n7);
        g.connectNode(n2, n7);
        g.connectNode(n2, n3);
        g.connectNode(n3, n4);
        g.connectNode(n4, n8);
        g.connectNode(n5, n6);
        g.connectNode(n5, n2);

        // Perform the DFS and BFS traversal of the graph
        for (Node n : g.nodes) {
            g.setRootNode(n);

            System.out.print("From node ");
            g.printNode(n);

            System.out.print("\nDFS (iterative): ");
            g.dfs();
            g.reset();

            System.out.print("\nDFS (recursive): ");
            g.dfs(g.getRootNode());
            g.reset();

            System.out.print("\nBFS (iterative): ");
            g.bfs();
            g.reset();
            System.out.println("\n");
        }
    }
}

【问题讨论】:

  • 我只进行了扫描,所以问题很可能出在其他地方,但我想知道您是否会遇到问题,因为您使用adjMatrix[indexOf(src)][indexOf(dst)] = 1 连接节点,但也不是使用@987654325 @ .在处理图表时,我会同时执行这两项任务,因此无论何时我搜索邻居时首先调用哪个顶点都无关紧要。
  • 酷,成功了!我要指出的第二件事是,当我们最初将根节点添加到 bfs 中的队列时,我们没有将根节点设置为已访问,并且在递归版本的 dfs 中也会出现类似的问题。事实上,在我看来,递归 dfs 中没有任何节点被标记为已访问。 (这与订购无关,但这些仍然很重要。)
  • 尽量不要在connectNode内部初始化adjMatrix,把它放到构造函数中,这样你的类的逻辑就更容易了。
  • 澄清问题:您想要达到的顺序到底是什么?当您使用队列和堆栈时,您对排序的控制很少,因此如果您需要对数据进行排序,可能需要使用不同的数据结构。
  • @HylianPikachu 每当给定相邻节点的列表时,节点应按键的升序排列。 (最小的节点总是第一个)。或者,如果一个节点与其他三个节点有连接,它会选择最小的节点先去。

标签: java graph-theory depth-first-search graph-traversal


【解决方案1】:

所以,我们已经讨论了您问题的第一部分,但我会在这里为那些关注的人重申它。每当使用图形和邻接矩阵时,初始化数组中元素的最佳方式可能是“双向”。

而不仅仅是使用以下,这需要首先列出特定顶点才能找到邻居:

adjMatrix[nodes.indexOf(src)][nodes.indexOf(dst)] = 1;

使用它会导致搜索不知道顶点顺序:

adjMatrix[nodes.indexOf(src)][nodes.indexOf(dst)] = 1;
adjMatrix[nodes.indexOf(dst)][nodes.indexOf(src)] = 1;

现在,订购。您希望按照从“最小”字母到“最大”字母的顺序输出顶点。我们将分别处理您的每一种数据结构。

在 BFS(迭代)中,您使用队列。队列是“先进先出”的。换句话说,每当您调用queue.poll() 时,将首先输出最近最少添加到队列中的元素。因此,您需要从最小到最大添加节点。

在 DFS(迭代)中,您使用堆栈。堆栈是“后进先出”。换句话说,无论何时调用stack.pop(),都会首先输出最近添加到堆栈中的元素。因此,您需要从大到小添加节点。

在 DFS(递归)中,您使用列表。列表本身没有“进出”顺序,因为我们可以按我们想要的任何顺序轮询它们,但最简单的做法是将列表从最小到最大排序并按顺序输出。

考虑到这一点,我们需要引入对图进行排序的协议。所有三个协议都使用getNeighbors(),因此我们将在调用该函数后立即对输出的列表进行排序。列表可以使用来自java.utils.Collections 的函数Collections.sort(List l) 进行排序,但我们首先需要修改您的节点类,以便Java 知道如何对节点进行排序。要进一步了解我正在做的事情的详细信息,您可以查看here,但这篇文章比我预期的要长得多,所以我将在这里展示代码,让有兴趣的人探索链接自己。

您将首先通过实现 Comparable&lt;Node&gt; 并添加 compareTo() 函数来调整您的 Node 类。

static class Node implements Comparable<Node>{
    public char label;
    public boolean visited = false;

    public Node(char label) {
        this.label = label;
    }

    @Override
    public int compareTo(Node that) {
       return Character.compare(this.label, that.label);
    }
}

然后,在我们想要从最小到最大排序列表的情况下,我们可以使用Collections.sort(neighbors)。当我们从大到小想要它时,我们可以使用Collections.sort(neighbors, Collections.reverseOrder())。我们的最终代码将如下所示:

// BFS traversal (iterative version)
public void bfs() {


    Queue<Node> queue = new LinkedList<Node>();

    queue.add(rootNode);

    while(!queue.isEmpty()) {

        Node node = queue.poll();
        printNode(node);
        node.visited = true;

        List<Node> neighbors = getNeighbors(node); 
        //NEW CODE: Sort our neighbors List!
        Collections.sort(neighbors);

        for ( int i = 0; i < neighbors.size(); i ++) {
            Node n = neighbors.get(i);

            if (n != null && n.visited != true) {
                queue.add(n);
                n.visited = true;
            }
        }

    }

}

// DFS traversal (iterative version)
public void dfs() {

    Stack<Node> stack = new Stack<Node>();
    stack.add(rootNode);
    while(!stack.isEmpty()){
        Node node = stack.pop();

        if(node.visited != true) {
            printNode(node);
            node.visited = true;
        }

        List<Node> neighbors = getNeighbors(node);
        //NEW CODE: Sort our neighbors List in reverse order!
        Collections.sort(neighbors, Collections.reverseOrder());

        for (int i = 0; i < neighbors.size(); i++) {
            Node n = neighbors.get(i);
            if(n != null && n.visited != true) {
                stack.add(n);
            }
        }

    }

}

// DFS traversal (recursive version)
public void dfs(Node n) {

    printNode(n);
    n.visited = true;
    List<Node> neighbors = getNeighbors(n);
    //NEW CODE: Sort our neighbors List!
    Collections.sort(neighbors);

    for (int i = 0; i < neighbors.size(); i ++) {
        Node node = neighbors.get(i);
        if(node != null && node.visited != true) {
            dfs(node);
        }
    }

}

【讨论】:

  • 当然!乐意效劳。在递归 DFS 上,我预计会增加一些毛羽,因为我们在完成对整个邻居列表的迭代之前会进入递归步骤。让我想想如何解决这个问题......
  • 嗯...所以看起来他们希望我们为迭代和递归版本获得相同的输出,所以我们可以通过先打印所有邻居,然后在之后进行递归来实现这一点? (在我看来,这是一种不好的做法,所以我不确定他们为什么会要求你这样做,但这似乎是最直接的解决方案。)
  • 等等,不!不要改变任何东西!我将您的代码复制并粘贴到 Eclipse 中,然后添加了我的逆序位,然后我在我的机器上得到了正确的答案,所以我认为答案在我们在这里构建的其他地方。 Stack 确实需要相反的顺序。如果你添加它,你应该得到正确的输出。
  • 是的,这就是我得到的。这是一些奇怪的酱汁……
  • 您的逆序排序直接在List&lt;Node&gt; neighbors = getNeighbors(node) 行之后和for 循环之前,对吧?
【解决方案2】:

我建议将您的问题分成更小的部分。

如果您想为无向图编写一个类,请先执行此操作并对其进行一些测试。

如果您想查看是否可以实现遍历,请确保您的图表首先工作。您还可以使用guava,它可以让您使用 MutableGraph(以及更多)。 Here 是在你使用 IntelliJ 的情况下如何安装它,here 是如何使用来自 guava 的图形。

还记得使用调试器来找出您的代码是否出错。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-12
    • 2018-12-15
    • 1970-01-01
    • 2017-02-12
    • 1970-01-01
    相关资源
    最近更新 更多