An extendable Graph datastructure.
// $Id: Graph.java 87 2010-04-09 02:12:11Z gabe.johnson@gmail.com $
//package org.six11.util.adt;
import java.util.List;
import java.util.Queue;
import java.util.Map;
import java.util.HashMap;
import java.util.ArrayList;
/**
* An extendable Graph datastructure. This provides a starting point for a host of graph
* datastructure applications, such as scheduling algorithms, decision trees, resource conflict
* resolution, etc. It also may make you better looking.
*
* The code here is a translation of the algorithms described in "Introduction to Algorithms" by
* Cormen, Leiserson, and Rivest (aka "Big Red"). I've also added my own special sauce--callbacks (a
* la Runnables) that you can use to follow the state of progress.
**/
public class Graph {
public final static int STATE_NONE = 0;
public final static int STATE_BFS = 1;
public final static int STATE_DFS = 2;
public final static int WHITE = 0;
public final static int GRAY = 1;
public final static int BLACK = 2;
public final static int UNKNOWN = 0;
public final static int TREE = 1;
public final static int BACK = 2;
public final static int FORWARD = 3;
public final static int CROSS = 4;
// these contain all the nodes and edges
protected List<Node> nodes;
protected List<Edge> edges;
// tells you if the graph edges are directed or undirected
protected boolean directed;
// search state indicates what the last search was, which tells you
// what information you may be able to find out. a STATE_* value.
transient protected int searchState = STATE_NONE;
// if edgeDataValid is true, this tells you if the graph has ...
transient protected boolean cycles;
transient protected List<Edge> cross;
transient protected boolean forward;
transient protected boolean tree;
// this tells us if the value of the above edge data vars can be
// trusted. if not, we have to directly look at the set of nodes.
transient protected boolean edgeDataValid;
transient protected List<Node> startNodes;
/**
* Create a directed graph.
*/
public Graph() {
nodes = new ArrayList<Node>();
edges = new ArrayList<Edge>();
directed = true;
}
public void setDirected(boolean directed) {
this.directed = directed;
}
public abstract static class NodeCallback {
public abstract void run(Node n);
}
public static class Node {
public Object data;
private int state = WHITE;
private int d = 0;
private int f = 0;
private Node p;
private boolean child;
// NodeCallbacks to invoke when entering a state.
private Map<Integer, NodeCallback> actions;
public Node(Object data) {
this.data = data;
this.actions = new HashMap<Integer, NodeCallback>();
}
protected boolean isVisited() {
return state > WHITE;
}
protected void setState(int state) {
if (this.state == state || state < WHITE || state > BLACK) {
return;
}
this.state = state;
// Debug.out("Graph", "Changed state of " + this + "#" + hashCode() + " to " + getStateStr());
if (state == WHITE) {
d = 0;
f = 0;
p = null;
child = false;
}
if (actions.get(state) != null) {
actions.get(state).run(Node.this);
}
}
public int getState() {
return state;
}
public String getStateStr() {
String ret = "UNKNOWN";
switch (state) {
case WHITE:
ret = "White";
break;
case GRAY:
ret = "Gray";
break;
case BLACK:
ret = "Black";
break;
}
return ret;
}
public int getDistance() {
return d;
}
public int getDiscovered() {
return d;
}
public int getFinished() {
return f;
}
protected void setDistance(int d) {
this.d = d;
}
protected void setDiscovered(int t) {
d = t;
}
protected void setFinished(int t) {
f = t;
}
public Node getPredecessor() {
return p;
}
protected void setPredecessor(Node p) {
this.p = p;
// Debug.out("Graph", "Changed parent of " + this + "#" + hashCode() + " to " + p);
}
protected void setChild() {
child = true;
}
protected boolean isChild() {
return child;
}
protected boolean isAncestor(Node a) {
boolean ret = false;
if (a != null && p != null) {
ret = a.equals(p) || p.isAncestor(a);
}
return ret;
}
public void setAction(int state, NodeCallback cb) {
actions.put(state, cb);
}
public boolean equals(Object other) {
boolean ret = false;
if (other instanceof Node) {
Node n = (Node) other;
ret = data.equals(n.data);
}
return ret;
}
public String toString() {
return (data == null ? super.toString() : data.toString());
}
}
public abstract static class EdgeCallback {
public abstract void run(Edge e);
}
public static class Edge {
public Node a;
public Node b;
public Object data;
private int mode;
private Map<Integer, EdgeCallback> actions;
public Edge(Node a, Node b, Object data) {
this.a = a;
this.b = b;
this.data = data;
actions = new HashMap<Integer, EdgeCallback>();
}
public void setAction(int state, EdgeCallback cb) {
actions.put(state, cb);
}
public boolean equals(Object other) {
boolean ret = false;
if (other instanceof Edge) {
Edge e = (Edge) other;
ret = (a.equals(e.a) && b.equals(e.b) && data.equals(e.data));
}
return ret;
}
protected void setMode(int mode) {
if (this.mode != mode) {
this.mode = mode;
if (actions.get(mode) != null) {
actions.get(mode).run(Edge.this);
}
}
}
public int getMode() {
return mode;
}
public String getModeStr() {
String ret = "UNKNOWN";
switch (mode) {
case FORWARD:
ret = "Forward";
break;
case TREE:
ret = "Tree";
break;
case BACK:
ret = "Back";
break;
case CROSS:
ret = "Cross";
break;
}
return ret;
}
public boolean isKnown() {
return mode != UNKNOWN;
}
public boolean isTree() {
return mode == TREE;
}
public boolean isBack() {
return mode == BACK;
}
public boolean isForward() {
return mode == FORWARD;
}
public boolean isCross() {
return mode == CROSS;
}
public String toString() {
String ns = a + "->" + b + " ";
return (data == null ? ns + super.toString() : ns + data.toString());
}
}
public void addNode(Node n) {
if (!nodes.contains(n)) {
nodes.add(n);
clearState();
}
}
public void removeNode(Node n) {
if (nodes.contains(n)) {
nodes.remove(n);
clearState();
}
}
/**
* Tells you if this graph contains a node whose state and data are both equal (== or equals(..))
* to the given Node.
*/
public boolean containsNode(Node n) {
return nodes.contains(n);
}
public boolean containsEdge(Edge e) {
return edges.contains(e);
}
public boolean containsEdge(Node a, Node b) {
boolean ret = false;
for (Edge e : edges) {
if (e.a.equals(a) && e.b.equals(b) || (!directed && e.b.equals(a) && e.a.equals(b))) {
ret = true;
break;
}
}
return ret;
}
public void addEdge(Edge e) {
if (!edges.contains(e)) {
edges.add(e);
clearState();
}
}
public void removeEdge(Edge e) {
if (edges.contains(e)) {
edges.remove(e);
clearState();
}
}
protected void clearState() {
searchState = STATE_NONE;
edgeDataValid = false;
startNodes = null;
}
public void bfs(Queue<Node> q) {
clearState();
for (Node n : nodes) {
n.setState(q.contains(n) ? GRAY : WHITE);
}
for (Edge e : edges) {
e.setMode(UNKNOWN);
}
for (Node n : q) {
n.setDistance(0);
n.setPredecessor(null);
}
while (!q.isEmpty()) {
Node n = q.remove();
List<Node> out = findNextNodes(n);
for (Node m : out) {
Edge e = findEdge(n, m);
if (e != null) {
if (m.getState() == WHITE) {
e.setMode(TREE);
} else if (m.getState() == GRAY) {
e.setMode(BACK);
}
}
if (!m.isVisited()) {
m.setDistance(n.getDistance() + 1);
m.setPredecessor(n);
m.setState(GRAY);
q.offer(m);
}
}
n.setState(BLACK);
}
searchState = STATE_BFS;
}
public void dfs(List<Node> these) {
clearState();
for (Node n : nodes) {
n.setState(WHITE);
n.setPredecessor(null);
}
for (Edge e : edges) {
e.setMode(UNKNOWN);
}
int time = 0;
time = dfs(null, these, time);
searchState = STATE_DFS;
}
public void dfs() {
dfs(nodes);
}
protected int dfs(Node p, List<Node> out, int time) {
for (Node n : out) {
Edge e = findEdge(p, n);
if (e != null) {
n.setChild();
}
if (n.getState() == WHITE) {
if (e != null) {
e.setMode(1);
}
n.setPredecessor(p);
time = dfsVisit(n, ++time);
} else if (p != null && n.getState() == GRAY && e != null) {
e.setMode(2);
} else if (p != null && e != null) {
if (n.isAncestor(p)) {
e.setMode(3);
} else {
e.setMode(4);
}
} else if (e != null && e.getMode() == 0) {
System.out.println(" --- ERROR ---------");
System.out.println(" - n.state: " + n.getState());
System.out.println(" - e: " + e);
System.out.println(" - p: " + p);
System.out.println(" -------------------");
}
}
return time;
}
protected int dfsVisit(Node n, int time) {
n.setState(GRAY);
n.setDiscovered(++time);
List<Node> out = findNextNodes(n);
dfs(n, out, time);
n.setState(BLACK);
n.setFinished(++time);
return time;
}
public List<Node> findNextNodes(Node n) {
List<Node> ret = new ArrayList<Node>();
for (Edge e : edges) {
if (e.a.equals(n)) {
ret.add(e.b);
}
if (!directed && e.b.equals(n)) {
ret.add(e.a);
}
}
return ret;
}
public Edge findEdge(Node a, Node b) {
Edge ret = null;
if (a != null && b != null) {
for (Edge e : edges) {
if (e.a.equals(a) && e.b.equals(b)) {
ret = e;
break;
} else if (!directed && e.a.equals(b) && e.b.equals(a)) {
ret = e;
break;
}
}
}
return ret;
}
public List<Edge> findEdgesEntering(Node n) {
List<Edge> ret = new ArrayList<Edge>();
for (Edge e : edges) {
if (e.b.equals(n)) {
ret.add(e);
}
}
return ret;
}
public List<Node> getNodes() {
return nodes;
}
public List<Node> getNodes(Object data) {
List<Node> ret = new ArrayList<Node>();
for (Node n : nodes) {
if (n.data.equals(data)) {
ret.add(n);
}
}
return ret;
}
public List<Edge> getEdges() {
return edges;
}
public List<Edge> getCrossEdges() {
computeEdgeData();
return cross;
}
public List<Node> findStartNodes() {
List<Node> ret;
if (searchState != STATE_DFS) {
dfs();
ret = findStartNodes();
} else {
if (startNodes == null) {
startNodes = new ArrayList<Node>();
for (Node n : nodes) {
if (!n.isChild()) {
startNodes.add(n);
}
}
}
ret = startNodes;
}
return ret;
}
public boolean hasCycles() {
computeEdgeData();
return cycles;
}
public boolean hasForward() {
computeEdgeData();
return forward;
}
public boolean hasCross() {
computeEdgeData();
return !cross.isEmpty();
}
public boolean hasTree() {
computeEdgeData();
return tree;
}
protected void computeEdgeData() {
if (searchState != STATE_DFS) {
dfs();
}
if (edgeDataValid)
return;
cycles = tree = forward = false;
cross = new ArrayList<Edge>();
for (Edge e : edges) {
if (e.isBack()) {
cycles = true;
}
if (e.isTree()) {
tree = true;
}
if (e.isForward()) {
forward = true;
}
if (e.isCross()) {
cross.add(e);
}
}
edgeDataValid = true;
}
}
Related examples in the same category