Is it true that the implementation of BFS, DFS and Dijkstra are almost the same, except that BFS uses queue, DFS uses stack, while Dijkstra uses min priority queue?
More precisely. Can we use the following code for all of BFS, DFS, and Dijkstra, with Q being a queue for BFS, and a stack for DFS, and a min priority queue for Dijkstra? Thanks!
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';
}
Let's say we have this graph, and want to find the shortest distances starting at A
:
Here is a simple NodeCollection
interface that allows for the operations needed for the traversal:
interface NodeCollection<E> {
void offer(E node);
E extract();
boolean isEmpty();
}
And the implementations for queue, stack and priority queue. Note that this interface and classes don't really need to be generic:
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(); }
}
Note that for PriorityQueue
to work as expected, the Node
class needs to provide a compareTo(Node)
method:
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();
}
}
Now here's the Graph
class. Note that the traverse
method takes a NodeCollection
instance, which will be used for storing nodes during the traversal.
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';
}
}
}
Finally here's the main
method, which traverses the same graph 3 times, once with each of the NodeCollection
types:
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}
Here's what the results look like this on the graph: