/*
* Author: tdanford
* Date: Nov 15, 2008
*/
package org.seqcode.gseutils.graphs;
import java.io.*;
import java.util.*;
import org.seqcode.gseutils.Pair;
import org.seqcode.gseutils.datastructures.Heap;
public class WeightedAlgorithms extends Algorithms {
public static void main(String[] args) {
//dijkstra_test(args);
//bellman_ford_test(args);
floyd_warshall_test(args);
}
public static void floyd_warshall_test(String[] args) {
WeightedGraph wg = figure_26_1();
WeightedAlgorithms algs = new WeightedAlgorithms(wg);
WeightedGraph[] paths = algs.floydWarshall();
String[] vertices = algs.vertices;
int source = 1, target = 0;
Pair<List<String>,Double> pathpair = algs.parsePathTree(paths[source], vertices[target]);
System.out.println(String.format("%s -> %s", vertices[source], vertices[target]));
System.out.println(String.format("Weight: %f", pathpair.getLast()));
System.out.println(String.format("Path: %s", pathpair.getFirst()));
}
/**
* Tests the Bellman-Ford implementation using the example from CLRS page 589 (Figure 24.4).
*
* @param args
*/
public static void bellman_ford_test(String[] args) {
WeightedGraph wg = figure_24_4();
String source = "s", target = "z";
WeightedAlgorithms algs = new WeightedAlgorithms(wg);
WeightedGraph paths = algs.bellmanFord(source);
Pair<List<String>,Double> pathpair = algs.parsePathTree(paths, target);
System.out.println(String.format("%s -> %s", source, target));
System.out.println(String.format("Weight: %f", pathpair.getLast()));
System.out.println(String.format("Path: %s", pathpair.getFirst()));
}
/**
* Runs, as a test of the dijkstra implementation, the example from CLRS on page 596 of
* the 2nd edition.
*
* @param args
*/
public static void dijkstra_test(String[] args) {
DirectedWeightedGraph wg = figure_25_5();
String source = "s", target = "x";
WeightedAlgorithms algs = new WeightedAlgorithms(wg);
WeightedGraph paths = algs.dijkstra(source);
Pair<List<String>,Double> pathpair = algs.parsePathTree(paths, target);
System.out.println(String.format("%s -> %s", source, target));
System.out.println(String.format("Weight: %f", pathpair.getLast()));
System.out.println(String.format("Path: %s", pathpair.getFirst()));
}
private WeightedGraph graph;
private String[] vertices;
public WeightedAlgorithms(WeightedGraph g) {
super(g);
graph = g;
vertices = graph.getVertices().toArray(new String[0]);
Arrays.sort(vertices);
}
/**
* A utility method, for manipulating weighted path-tree objects (which are
* represented as WeightedGraphs in this implementation).
*
* For a given tree and a given "target" node, this method will follow links in the
* path tree back to the root -- this gives the shortest path (in reverse order, of course)
* of the path from root --> target in the original graph.
*
* This method also returns the ultimate weight of that (shortest) path.
*
* @param g The path-tree, which must have been calculated with respect to a given source.
* @param target The ultimate target in the graph.
* @return
*/
public Pair<List<String>, Double> parsePathTree(WeightedGraph g, String target) {
LinkedList<String> path = new LinkedList<String>();
Double weight = g.weight(target);
while(target != null) {
path.addFirst(target);
Set<String> nbs = g.getNeighbors(target);
Iterator<String> itr = nbs.iterator();
target = itr.hasNext() ? itr.next() : null;
}
return new Pair<List<String>,Double>(path, weight);
}
/**
* Performs the Floyd-Warshall all-pairs shortest-paths algorithm. Returns an array of path
* tree matrices.
* @return A path-tree matrix, where matrix[i] is the path-tree for paths originating from vertices[i].
*/
public WeightedGraph[] floydWarshall() {
DirectedWeightedGraph[][] karray = new DirectedWeightedGraph[][] {
createFloydWarshallMatrix(),
createFloydWarshallMatrix()
};
System.out.println("Init:");
printFloydWarshallMatrix(karray[0], System.out);
int thisk = 0, lastk = 0;
for(int k = 1; k <= vertices.length; k++) {
thisk = k % 2;
lastk = 1-thisk;
floydWarshallStep(k-1, karray[thisk], karray[lastk]);
System.out.println(String.format("%d:", k));
printFloydWarshallMatrix(karray[thisk], System.out);
}
WeightedGraph[] finalMatrix = new WeightedGraph[karray[0].length];
for(int i = 0; i < karray[0].length; i++) {
finalMatrix[i] = karray[thisk][i];
}
return finalMatrix;
}
public void printFloydWarshallMatrix(DirectedWeightedGraph[] karray, PrintStream ps) {
for(int j = 0; j < vertices.length; j++) {
ps.print(String.format("\t%s", vertices[j]));
}
ps.println();
for(int i = 0; i < karray.length; i++) {
ps.print(vertices[i]);
for(int j = 0; j < vertices.length; j++) {
Double w = karray[i].weight(vertices[j]);
String wstr = w != null ? String.format("%.1f", w) : "*";
ps.print(String.format("\t%s", wstr));
}
ps.println();
}
}
private void floydWarshallStep(int k, DirectedWeightedGraph[] thisK, DirectedWeightedGraph[] lastK) {
for(int i = 0; i < vertices.length; i++) {
for(int j = 0; j < vertices.length; j++) {
String lastPath = lastK[i].chooseNeighbor(vertices[j]);
Double lastWeight = lastPath != null ? graph.weight(lastPath, vertices[j]) : null;
thisK[i].removeEdges(vertices[j]);
Double dij = lastK[i].weight(vertices[j]);
Double dik = lastK[i].weight(vertices[k]);
Double dkj = lastK[k].weight(vertices[j]);
Double ikjWeight = add(dik, dkj);
Boolean isBetter = lessThan(ikjWeight, dij);
if(isBetter) {
thisK[i].addWeightedEdge(vertices[j], vertices[k], graph.weight(vertices[k], vertices[j]));
thisK[i].setWeight(vertices[j], ikjWeight);
} else if (lastPath != null) {
thisK[i].addWeightedEdge(vertices[j], lastPath, lastWeight);
thisK[i].setWeight(vertices[j], dij);
}
}
}
}
private Double add(Double v1, Double v2) {
return v1 == null || v2 == null ? null : v1 + v2;
}
private Boolean lessThan(Double v1, Double v2) {
if(v1 == null) {
return false;
} else if (v2 == null) {
return true;
} else {
return v1 < v2;
}
}
private DirectedWeightedGraph[] createFloydWarshallMatrix() {
DirectedWeightedGraph[] matrix = new DirectedWeightedGraph[vertices.length];
for(int i = 0; i < vertices.length; i++) {
matrix[i] = new DirectedWeightedGraph(null);
for(int j = 0; j < vertices.length; j++) {
matrix[i].addVertex(vertices[j]);
}
for(int j = 0; j < vertices.length; j++) {
if(i == j) {
matrix[i].setWeight(vertices[j], 0.0);
} else if (graph.isNeighbor(vertices[i], vertices[j])) {
Double w = graph.weight(vertices[i], vertices[j]);
matrix[i].setWeight(vertices[j], w);
matrix[i].addWeightedEdge(vertices[j], vertices[i], w);
}
}
}
return matrix;
}
public WeightedGraph bellmanFord(String source) {
if(!graph.getVertices().contains(source)) {
throw new IllegalArgumentException(source);
}
DirectedWeightedGraph pathtree = initializePathTree();
pathtree.setWeight(source, 0.0);
boolean hasNegativeLoop = false;
int V = graph.getVertices().size();
for(int i = 0; i < V-1; i++) {
for(String n1 : graph.getVertices()) {
for(String n2 : graph.getNeighbors(n1)) {
double w = graph.weight(n1, n2);
relax(pathtree, n1, n2, w);
}
}
}
outerLoop: for(String u : graph.getVertices()) {
double du = pathtree.weight(u);
for(String v : graph.getNeighbors(u)) {
double dv = pathtree.weight(v);
if(dv > du + graph.weight(u, v)) {
hasNegativeLoop = true;
break outerLoop; // ends the outer-most for loop.
}
}
}
return hasNegativeLoop ? null : pathtree;
}
/**
* Returns a weighted graph of shortest-path links.
*
* For a given v2 \in G = dijkstra(v1), the
* weight(v2) in G is the weight of the shortest path v1 -> v2.
*
* Furthermore, v2 has exactly one neighbor in G, v3, which is the previous
* node on that shortest path from v2 back to v2. The weight(v2, v3) is the
* weight of the edge v3->v2 in the *original* graph which is on that shortest path.
*
* dijkstra() throws an IllegalArgumentException if it encounters a negative-weight edge.
*
* @param source The source node in the original graph.
* @return The weighted path-tree.
*/
public WeightedGraph dijkstra(String source) {
if(!graph.getVertices().contains(source)) {
throw new IllegalArgumentException(source);
}
DirectedWeightedGraph pathtree = initializePathTree();
pathtree.setWeight(source, 0.0);
Heap<WeightedNode> Q = new Heap<WeightedNode>(-1);
for(String vertex : pathtree.getVertices()) {
Q.insert(new WeightedNode(vertex, pathtree.weight(vertex)));
}
while(Q.size() > 0) {
//System.out.println(String.format("\nQ: %s", Q.asList().toString()));
WeightedNode u = Q.removeFirst();
//System.out.println(String.format("u: %s", u.toString()));
if(u.weight == null) {
// The rest of the graph is unconnected -- so we just return the pathtree.
return pathtree;
} else {
for(String v : graph.getNeighbors(u.node)) {
Double w = graph.weight(u.node, v);
if(w < 0.0) {
// Dijkstra's algorithm doesn't work, when there are negative weight edges.
throw new IllegalArgumentException(String.format("%s->%s has negative weight %f",
u.node, v, w));
}
relax(Q, pathtree, u, v, w);
}
}
}
return pathtree;
}
/**
* This is totally ugly.
*
* The RELAX() method described in CLRS is actually tricky --
* it requires an (implicit) update of the Q heap, and that requires knowing where each
* old key was in the heap so we can call increaseKey() on it ... so I do that here with a
* linear search through the heap (implemented in the increase() method, in Heap.java), but
* this is probably sub-optimal... sigh.
*
* @param Q See the note above.
* @param pathtree Stores the 'd' and 'pi' variables from CLRS, implicitly.
* @param u
* @param v
* @param w
*/
private void relax(Heap<WeightedNode> Q, DirectedWeightedGraph pathtree,
WeightedNode u, String v, Double w) {
Double uweight = pathtree.weight(u.node);
Double oldvweight = pathtree.weight(v);
Double newvweight = uweight != null ? uweight + w : null;
WeightedNode oldvn = new WeightedNode(v, oldvweight);
WeightedNode newvn = new WeightedNode(v, newvweight);
if(newvn.compareTo(oldvn) == -1) {
for(String nn : pathtree.getNeighbors(v)) {
pathtree.removeEdge(v, nn);
}
pathtree.addEdge(v, u.node);
pathtree.setWeight(v, newvweight);
pathtree.setWeight(v, u.node, w);
//System.out.println(String.format("%s => %s", oldvn.toString(), newvn.toString()));
Q.increase(oldvn, newvn);
}
}
private void relax(DirectedWeightedGraph pathtree, String u, String v, Double w) {
Double uweight = pathtree.weight(u);
Double oldvweight = pathtree.weight(v);
Double newvweight = uweight != null ? uweight + w : null;
if(newvweight == null) { return; }
if(oldvweight == null || newvweight < oldvweight) {
for(String nn : pathtree.getNeighbors(v)) {
pathtree.removeEdge(v, nn);
}
pathtree.addEdge(v, u);
pathtree.setWeight(v, newvweight);
pathtree.setWeight(v, u, w);
}
}
private DirectedWeightedGraph initializePathTree() {
DirectedWeightedGraph tree = new DirectedWeightedGraph(null);
for(String node : graph.getVertices()) {
tree.addVertex(node);
}
return tree;
}
private class WeightedNode implements Comparable<WeightedNode> {
public String node;
public Double weight; // a null weight will represent 'infinity.'
public WeightedNode(String n, Double w) {
node = n; weight = w;
}
public WeightedNode(String n) {
node = n;
weight = null;
}
public String toString() {
if(weight != null) {
return String.format("%s (%.2f)", node, weight);
} else {
return String.format("%s (inf)", node);
}
}
public int compareTo(WeightedNode n) {
if(weight != null || n.weight != null) {
if(weight != null && n.weight != null) {
if(weight < n.weight) { return -1; }
if(weight > n.weight) { return 1; }
} else {
if(weight == null) { return 1; }
if(n.weight == null) { return -1; }
}
}
//return node.compareTo(n.node);
return 0;
}
public int hashCode() {
return node.hashCode();
}
public boolean equals(Object o) {
if(!(o instanceof WeightedNode)) { return false; }
WeightedNode w = (WeightedNode)o;
return w.node.equals(node);
}
}
/**
* The example from CLRS, used to illustrate Dijkstra's Algorithm.
* @return
*/
public static DirectedWeightedGraph figure_25_5() {
DirectedWeightedGraph wg = new DirectedWeightedGraph(1.0);
wg.addVertex("s");
wg.addVertex("t");
wg.addVertex("x");
wg.addVertex("y");
wg.addVertex("z");
wg.addWeightedEdge("s", "t", 10.0);
wg.addWeightedEdge("s", "y", 5.0);
wg.addWeightedEdge("t", "y", 2.0);
wg.addWeightedEdge("y", "t", 3.0);
wg.addWeightedEdge("y", "x", 9.0);
wg.addWeightedEdge("t", "x", 1.0);
wg.addWeightedEdge("y", "z", 2.0);
wg.addWeightedEdge("x", "z", 4.0);
wg.addWeightedEdge("z", "x", 6.0);
wg.addWeightedEdge("z", "s", 7.0);
return wg;
}
public static WeightedGraph figure_26_1() {
DirectedWeightedGraph wg = new DirectedWeightedGraph(null);
wg.addVertex("1");
wg.addVertex("2");
wg.addVertex("3");
wg.addVertex("4");
wg.addVertex("5");
wg.addWeightedEdge("1", "5", -4.0);
wg.addWeightedEdge("1", "2", 3.0);
wg.addWeightedEdge("1", "3", 8.0);
wg.addWeightedEdge("2", "5", 7.0);
wg.addWeightedEdge("2", "4", 1.0);
wg.addWeightedEdge("3", "2", 4.0);
wg.addWeightedEdge("4", "3", -5.0);
wg.addWeightedEdge("4", "1", 2.0);
wg.addWeightedEdge("5", "4", 6.0);
return wg;
}
public static WeightedGraph figure_24_4() {
DirectedWeightedGraph wg = new DirectedWeightedGraph(null);
wg.addVertex("s");
wg.addVertex("t");
wg.addVertex("x");
wg.addVertex("y");
wg.addVertex("z");
wg.addWeightedEdge("s", "t", 6.0);
wg.addWeightedEdge("s", "y", 7.0);
wg.addWeightedEdge("t", "y", 8.0);
wg.addWeightedEdge("t", "x", 5.0);
wg.addWeightedEdge("t", "z", -4.0);
wg.addWeightedEdge("y", "x", -3.0);
wg.addWeightedEdge("y", "z", 9.0);
wg.addWeightedEdge("x", "t", -2.0);
wg.addWeightedEdge("z", "s", 2.0);
wg.addWeightedEdge("z", "x", 7.0);
return wg;
}
}