/*
* Copyright (c) 2008, SQL Power Group Inc.
*
* This file is part of SQL Power Library.
*
* SQL Power Library is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* SQL Power Library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package ca.sqlpower.graph;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
/**
* Class for performing a breadth-first search of a graph starting
* from a given node. Includes a callback (listener) mechanism for
* clients to perform some custom action at each node along the way.
*
* @param V the vertex type of the graph. Doesn't really matter to
* the algorithm implemented here, but it helps with type safety in
* the listener.
* @param E The edge type of the graph. Doesn't really matter to
* the algorithm implemented here, but it helps with type safety in
* the listener.
*/
public class BreadthFirstSearch<V, E> {
/**
* The algorithm uses three states for each node, which we think
* of as three colours.
*/
private enum Colour {
/**
* The undiscovered state.
*/
WHITE,
/**
* The discovered but not finished state.
*/
GRAY,
/**
* The discovered and finished state.
*/
BLACK
}
/**
* The listeners who want to be notified of various aspects of the
* search while it is in progress.
*/
private final List<BreadthFirstSearchListener<V>> searchListeners =
new ArrayList<BreadthFirstSearchListener<V>>();
/**
* A comparator used to order the adjacent nodes, ignored if null.
*/
private Comparator<V> comparator;
public BreadthFirstSearch() {
}
/**
* Performs a breadth-first search of the given graph, starting
* with the given node. This algorithm is described in "Introduction
* to Algorithms" by Cormen et al, Chapter 23.
*
* @param model
* @param startingNode
* @return the list of nodes discovered, in the order they were discovered
*/
public List<V> performSearch(GraphModel<V, E> model, V startingNode) {
List<V> discoveredNodes = new ArrayList<V>();
Map<V, Colour> colour = new HashMap<V, Colour>();
Map<V, Integer> depth = new HashMap<V, Integer>();
Map<V, V> predecessor = new HashMap<V, V>();
for (V u : model.getNodes()) {
colour.put(u, Colour.WHITE);
depth.put(u, Integer.MAX_VALUE);
predecessor.put(u, null);
}
colour.put(startingNode, Colour.GRAY);
depth.put(startingNode, 0);
predecessor.put(startingNode, null);
fireNodeDiscovered(startingNode);
discoveredNodes.add(startingNode);
Queue<V> queue = new LinkedList<V>();
queue.add(startingNode);
while (!queue.isEmpty()) {
V u = queue.element();
Collection<V> adjacentNodes = model.getAdjacentNodes(u);
if (comparator != null) {
adjacentNodes = new ArrayList<V>(adjacentNodes);
Collections.sort((List<V>) adjacentNodes, comparator);
}
for (V v : adjacentNodes) {
if (colour.get(v) == Colour.WHITE) {
colour.put(v, Colour.GRAY);
depth.put(v, depth.get(u) + 1);
predecessor.put(v, u);
queue.add(v);
fireNodeDiscovered(v);
discoveredNodes.add(v);
}
}
queue.remove();
colour.put(u, Colour.BLACK);
}
return discoveredNodes;
}
/**
* Notifies all search listeners that the given node was just discovered.
*/
private void fireNodeDiscovered(V node) {
for (int i = searchListeners.size()-1; i >= 0; i--) {
BreadthFirstSearchListener<V> l = searchListeners.get(i);
l.nodeDiscovered(node);
}
}
/**
* Adds the given listener to the list of clients interested in events
* that occur during the breadth first search.
*
* @param l The listener to add. Must not be null.
*/
public void addBreadthFirstSearchListener(BreadthFirstSearchListener<V> l) {
if (l == null) throw new NullPointerException("Null listeners not allowed");
searchListeners.add(l);
}
/**
* Removes the given listener from this search object's listener list.
*
* @param l The listener to remove. If not present in the listener
* list, the call to this method has no effect.
*/
public void removeBreadthFirstSearchListener(BreadthFirstSearchListener<V> l) {
searchListeners.remove(l);
}
/**
* Returns the comparator used to order the adjacent nodes.
*/
public Comparator<V> getComparator() {
return comparator;
}
/**
* Sets a custom comparator to order the adjacent nodes. Setting a null
* comparator makes it not use one.
*/
public void setComparator(Comparator<V> comparator) {
this.comparator = comparator;
}
}