实施BFS,DFS和Dijkstra

时间:2012-01-05 19:04:11

标签: graph-algorithm dijkstra breadth-first-search depth-first-search

BFS,DFS和Dijkstra的实现几乎是一样的,除了BFS使用队列,DFS使用堆栈,而Dijkstra使用最小优先级队列?

更确切地说。我们是否可以对所有BFS,DFS和Dijkstra使用以下代码,Q是BFS的队列,DFS的堆栈和Dijkstra的最小优先级队列?谢谢!

Init d[]=Inf; // distance from the node s
Init c[]='w'; // color of nodes: 'w':undiscovered, 'g':discovered, 'b':fully explored
Init p[]=null; // previous node in the path
c[s]='g';
d[s]=0;
Q.push(s);
while(!Q.empty()) {
    u = Q.front();
    Q.pop();
    for v in adj[u] {
        if(c(v)=='w') {
            c[v]='g';
            if(d[u]+w(u,v)<d[v]) {
                d[v]=d[u]+w(u,v);
                p[v]=u;
            }
            Q.push(v);
        }
    }
    c[u]='b';
}

3 个答案:

答案 0 :(得分:8)

假设我们有这个图表,并希望找到从A开始的最短距离:

Graph

这是一个简单的NodeCollection接口,允许遍历所需的操作:

interface NodeCollection<E> {
    void offer(E node);
    E extract();
    boolean isEmpty();
}

队列,堆栈和优先级队列的实现。请注意,此接口和类实际上不需要是通用的:

static class NodeQueue<E> implements NodeCollection<E> {
    private final Queue<E> queue = new LinkedList<E>();
    @Override public void offer(E node) { queue.offer(node); }
    @Override public E extract() { return queue.poll(); }
    @Override public boolean isEmpty() { return queue.isEmpty(); }
}

static class NodeStack<E> implements NodeCollection<E> {
    private final Stack<E> stack = new Stack<E>();
    @Override public void offer(E node) { stack.push(node); }
    @Override public E extract() { return stack.pop(); }
    @Override public boolean isEmpty() { return stack.isEmpty(); }
}

static class NodePriorityQueue<E> implements NodeCollection<E> {
    private final PriorityQueue<E> pq = new PriorityQueue<E>();
    @Override public void offer(E node) { pq.add(node); }
    @Override public E extract() { return pq.poll(); }
    @Override public boolean isEmpty() { return pq.isEmpty(); }
}

请注意,要使PriorityQueue按预期工作,Node类需要提供compareTo(Node)方法:

static class Node implements Comparable<Node> {
    final String name;
    Map<Node, Integer> neighbors;
    int dist = Integer.MAX_VALUE;
    Node prev = null;
    char color = 'w';

    Node(String name) {
        this.name = name;
        this.neighbors = Maps.newHashMap();
    }

    @Override public int compareTo(Node o) {
        return ComparisonChain.start().compare(this.dist, o.dist).result();
    }
}

现在这是Graph课程。请注意,traverse方法采用NodeCollection实例,该实例将用于在遍历期间存储节点。

static class Graph {
    Map<String, Node> nodes = Maps.newHashMap();

    void addEdge(String fromName, String toName, int weight) {
        Node from = getOrCreate(fromName);
        Node to = getOrCreate(toName);
        from.neighbors.put(to, weight);
        to.neighbors.put(from, weight);
    }

    Node getOrCreate(String name) {
        if (!nodes.containsKey(name)) {
            nodes.put(name, new Node(name));
        }
        return nodes.get(name);
    }

    /**
     * Traverses this graph starting at the given node and returns a map of shortest paths from the start node to
     * every node.
     *
     * @param startName start node
     * @return shortest path for each node in the graph
     */
    public Map<String, Integer> traverse(String startName, NodeCollection<Node> collection) {
        assert collection.isEmpty();
        resetNodes();

        Node start = getOrCreate(startName);
        start.dist = 0;
        collection.offer(start);

        while (!collection.isEmpty()) {
            Node curr = collection.extract();
            curr.color = 'g';
            for (Node neighbor : curr.neighbors.keySet()) {
                if (neighbor.color == 'w') {
                    int thisPathDistance = curr.dist + curr.neighbors.get(neighbor);
                    if (thisPathDistance < neighbor.dist) {
                        neighbor.dist = thisPathDistance;
                        neighbor.prev = curr;
                    }
                    collection.offer(neighbor);
                }
            }
            curr.color = 'b';
        }

        Map<String, Integer> shortestDists = Maps.newTreeMap();
        for (Node node : nodes.values()) {
            shortestDists.put(node.name, node.dist);
        }
        return shortestDists;
    }

    private void resetNodes() {
        for (Node node : nodes.values()) {
            node.dist = Integer.MAX_VALUE;
            node.prev = null;
            node.color = 'w';
        }
    }
}

最后,这是main方法,该方法遍历同一个图形3次,每种NodeCollection类型遍历一次:

private static Graph initGraph() {
    Graph graph = new Graph();
    graph.addEdge("A", "B", 2);
    graph.addEdge("B", "C", 2);
    graph.addEdge("C", "D", 2);
    graph.addEdge("D", "E", 2);
    graph.addEdge("E", "F", 2);
    graph.addEdge("F", "L", 2);

    graph.addEdge("A", "G", 10);
    graph.addEdge("G", "H", 10);
    graph.addEdge("H", "I", 10);
    graph.addEdge("I", "J", 10);
    graph.addEdge("J", "K", 10);
    graph.addEdge("K", "L", 10);

    return graph;
}

public static void main(String[] args) {
    Graph graph = initGraph();
    System.out.println("Queue (BFS):\n" + graph.traverse("A", new NodeQueue<Node>()));
    System.out.println("Stack (DFS):\n" + graph.traverse("A", new NodeStack<Node>()));
    System.out.println("PriorityQueue (Dijkstra):\n" + graph.traverse("A", new NodePriorityQueue<Node>()));
}

结果!

Queue (BFS):
{A=0, B=2, C=4, D=6, E=8, F=10, G=10, H=20, I=30, J=40, K=22, L=12}
Stack (DFS):
{A=0, B=2, C=4, D=66, E=64, F=62, G=10, H=20, I=30, J=40, K=50, L=60}
PriorityQueue (Dijkstra):
{A=0, B=2, C=4, D=6, E=8, F=10, G=10, H=20, I=30, J=32, K=22, L=12}

请注意,DFS有时会首先采用顶部分支,产生不同但对称的结果。

以下是图表上的结果:

Results

答案 1 :(得分:2)

关于BFS与DFS的问题:是和否,但更多的是“否”而不是“是”。

如果你关心的只是前向遍历顺序,即算法发现图形的新顶点的顺序,那么是:你可以采取经典的BFS算法,用LIFO堆栈替换FIFO队列,你将得到伪DFS算法。

但是,我称之为 -DFS算法,因为它与经典DFS并不完全相同。

以这种方式获得的DFS算法确实会生成真正的 DFS顶点发现顺序。但是,它仍然会与其他一些注册器中的经典DFS不同。您可以在算法(或维基百科)的任何书籍中找到经典DFS的描述,您将看到该算法的结构与BFS明显不同。这样做是有原因的。除了生成正确的 DFS顶点发现顺序之外,经典DFS还提供了一些额外的好处。这些额外的好处包括

  • 降低峰值内存消耗。在传统的DFS实现中,每个时刻的堆栈大小等于从搜索原点到当前顶点的路径长度。在伪DFS中,每个时刻的堆栈大小等于从搜索原点到当前顶点的所有顶点的度数之和。这意味着伪DFS算法的峰值内存消耗可能会高得多。

    对于一个极端的例子,考虑一个“雪花”图,该图由中心的单个顶点直接连接到围绕它的1000个顶点组成。经典的DFS将遍历此图形,最大堆栈深度为1.同时,伪DFS将通过将所有1000个顶点推入堆栈(以BFS方式)开始,导致峰值堆栈深度为1000.这是非常不同的。 / p>

  • 回溯。经典的DFS算法是一种真正的递归算法。作为除前向遍历顺序(即顶点发现顺序)之外的递归算法,它还为您提供后向遍历顺序(回溯)。在经典的DFS中,您可以多次访问每个顶点:第一次是在第一次发现它时,然后当您从其中一个后代顶点返回以前进到下一个后代顶点时,最后是最后一次你已经处理了它的所有后代。许多基于DFS的算法都建立在捕获和处理这些访问的基础之上。例如,拓扑排序算法是经典的DFS,它按照上次DFS访问的顺序输出顶点。如上所述,伪DFS算法只能为您提供对第一个事件(顶点发现)的清晰访问,但不会注册任何回溯事件。

答案 2 :(得分:0)

是的,这是真的。许多有用的算法都有类似的模式。例如,对于图形特征向量,Power Iteration算法,如果更改起始向量和正交化向量,则会得到一整套有用但相关的算法。在这种情况下,它被称为ABS投影。

在这种情况下,它们都建立在“增量添加”到树原语之上。这就是我们如何选择要添加的边/顶点来确定树的类型,从而确定导航的类型。