initial commit

This commit is contained in:
josch 2012-07-03 17:14:13 +02:00
commit 26adaf464e
6 changed files with 599 additions and 0 deletions

View file

@ -0,0 +1,45 @@
package de.normalisiert.utils.graphs;
import java.util.Vector;
/**
* Calculates the adjacency-list for a given adjacency-matrix.
*
*
* @author Frank Meyer, web@normalisiert.de
* @version 1.0, 26.08.2006
*
*/
public class AdjacencyList {
/**
* Calculates a adjacency-list for a given array of an adjacency-matrix.
*
* @param adjacencyMatrix array with the adjacency-matrix that represents
* the graph
* @return int[][]-array of the adjacency-list of given nodes. The first
* dimension in the array represents the same node as in the given
* adjacency, the second dimension represents the indicies of those nodes,
* that are direct successornodes of the node.
*/
public static int[][] getAdjacencyList(boolean[][] adjacencyMatrix) {
int[][] list = new int[adjacencyMatrix.length][];
for (int i = 0; i < adjacencyMatrix.length; i++) {
Vector v = new Vector();
for (int j = 0; j < adjacencyMatrix[i].length; j++) {
if (adjacencyMatrix[i][j]) {
v.add(new Integer(j));
}
}
list[i] = new int[v.size()];
for (int j = 0; j < v.size(); j++) {
Integer in = (Integer) v.get(j);
list[i][j] = in.intValue();
}
}
return list;
}
}

View file

@ -0,0 +1,165 @@
package de.normalisiert.utils.graphs;
import java.util.List;
import java.util.Vector;
/**
* Searchs all elementary cycles in a given directed graph. The implementation
* is independent from the concrete objects that represent the graphnodes, it
* just needs an array of the objects representing the nodes the graph
* and an adjacency-matrix of type boolean, representing the edges of the
* graph. It then calculates based on the adjacency-matrix the elementary
* cycles and returns a list, which contains lists itself with the objects of the
* concrete graphnodes-implementation. Each of these lists represents an
* elementary cycle.<br><br>
*
* The implementation uses the algorithm of Donald B. Johnson for the search of
* the elementary cycles. For a description of the algorithm see:<br>
* Donald B. Johnson: Finding All the Elementary Circuits of a Directed Graph.
* SIAM Journal on Computing. Volumne 4, Nr. 1 (1975), pp. 77-84.<br><br>
*
* The algorithm of Johnson is based on the search for strong connected
* components in a graph. For a description of this part see:<br>
* Robert Tarjan: Depth-first search and linear graph algorithms. In: SIAM
* Journal on Computing. Volume 1, Nr. 2 (1972), pp. 146-160.<br>
*
* @author Frank Meyer, web_at_normalisiert_dot_de
* @version 1.2, 22.03.2009
*
*/
public class ElementaryCyclesSearch {
/** List of cycles */
private List cycles = null;
/** Adjacency-list of graph */
private int[][] adjList = null;
/** Graphnodes */
private Object[] graphNodes = null;
/** Blocked nodes, used by the algorithm of Johnson */
private boolean[] blocked = null;
/** B-Lists, used by the algorithm of Johnson */
private Vector[] B = null;
/** Stack for nodes, used by the algorithm of Johnson */
private Vector stack = null;
/**
* Constructor.
*
* @param matrix adjacency-matrix of the graph
* @param graphNodes array of the graphnodes of the graph; this is used to
* build sets of the elementary cycles containing the objects of the original
* graph-representation
*/
public ElementaryCyclesSearch(boolean[][] matrix, Object[] graphNodes) {
this.graphNodes = graphNodes;
this.adjList = AdjacencyList.getAdjacencyList(matrix);
}
/**
* Returns List::List::Object with the Lists of nodes of all elementary
* cycles in the graph.
*
* @return List::List::Object with the Lists of the elementary cycles.
*/
public List getElementaryCycles() {
this.cycles = new Vector();
this.blocked = new boolean[this.adjList.length];
this.B = new Vector[this.adjList.length];
this.stack = new Vector();
StrongConnectedComponents sccs = new StrongConnectedComponents(this.adjList);
int s = 0;
while (true) {
SCCResult sccResult = sccs.getAdjacencyList(s);
if (sccResult != null && sccResult.getAdjList() != null) {
Vector[] scc = sccResult.getAdjList();
s = sccResult.getLowestNodeId();
for (int j = 0; j < scc.length; j++) {
if ((scc[j] != null) && (scc[j].size() > 0)) {
this.blocked[j] = false;
this.B[j] = new Vector();
}
}
this.findCycles(s, s, scc);
s++;
} else {
break;
}
}
return this.cycles;
}
/**
* Calculates the cycles containing a given node in a strongly connected
* component. The method calls itself recursivly.
*
* @param v
* @param s
* @param adjList adjacency-list with the subgraph of the strongly
* connected component s is part of.
* @return true, if cycle found; false otherwise
*/
private boolean findCycles(int v, int s, Vector[] adjList) {
boolean f = false;
this.stack.add(new Integer(v));
this.blocked[v] = true;
for (int i = 0; i < adjList[v].size(); i++) {
int w = ((Integer) adjList[v].get(i)).intValue();
// found cycle
if (w == s) {
Vector cycle = new Vector();
for (int j = 0; j < this.stack.size(); j++) {
int index = ((Integer) this.stack.get(j)).intValue();
cycle.add(this.graphNodes[index]);
}
this.cycles.add(cycle);
f = true;
} else if (!this.blocked[w]) {
if (this.findCycles(w, s, adjList)) {
f = true;
}
}
}
if (f) {
this.unblock(v);
} else {
for (int i = 0; i < adjList[v].size(); i++) {
int w = ((Integer) adjList[v].get(i)).intValue();
if (!this.B[w].contains(new Integer(v))) {
this.B[w].add(new Integer(v));
}
}
}
this.stack.remove(new Integer(v));
return f;
}
/**
* Unblocks recursivly all blocked nodes, starting with a given node.
*
* @param node node to unblock
*/
private void unblock(int node) {
this.blocked[node] = false;
Vector Bnode = this.B[node];
while (Bnode.size() > 0) {
Integer w = (Integer) Bnode.get(0);
Bnode.remove(0);
if (this.blocked[w.intValue()]) {
this.unblock(w.intValue());
}
}
}
}

View file

@ -0,0 +1,32 @@
package de.normalisiert.utils.graphs;
import java.util.HashSet;
import java.util.Set;
import java.util.Vector;
public class SCCResult {
private Set nodeIDsOfSCC = null;
private Vector[] adjList = null;
private int lowestNodeId = -1;
public SCCResult(Vector[] adjList, int lowestNodeId) {
this.adjList = adjList;
this.lowestNodeId = lowestNodeId;
this.nodeIDsOfSCC = new HashSet();
if (this.adjList != null) {
for (int i = this.lowestNodeId; i < this.adjList.length; i++) {
if (this.adjList[i].size() > 0) {
this.nodeIDsOfSCC.add(new Integer(i));
}
}
}
}
public Vector[] getAdjList() {
return adjList;
}
public int getLowestNodeId() {
return lowestNodeId;
}
}

View file

@ -0,0 +1,279 @@
package de.normalisiert.utils.graphs;
import java.util.Vector;
/**
* This is a helpclass for the search of all elementary cycles in a graph
* with the algorithm of Johnson. For this it searches for strong connected
* components, using the algorithm of Tarjan. The constructor gets an
* adjacency-list of a graph. Based on this graph, it gets a nodenumber s,
* for which it calculates the subgraph, containing all nodes
* {s, s + 1, ..., n}, where n is the highest nodenumber in the original
* graph (e.g. it builds a subgraph with all nodes with higher or same
* nodenumbers like the given node s). It returns the strong connected
* component of this subgraph which contains the lowest nodenumber of all
* nodes in the subgraph.<br><br>
*
* For a description of the algorithm for calculating the strong connected
* components see:<br>
* Robert Tarjan: Depth-first search and linear graph algorithms. In: SIAM
* Journal on Computing. Volume 1, Nr. 2 (1972), pp. 146-160.<br>
* For a description of the algorithm for searching all elementary cycles in
* a directed graph see:<br>
* Donald B. Johnson: Finding All the Elementary Circuits of a Directed Graph.
* SIAM Journal on Computing. Volumne 4, Nr. 1 (1975), pp. 77-84.<br><br>
*
* @author Frank Meyer, web_at_normalisiert_dot_de
* @version 1.1, 22.03.2009
*
*/
public class StrongConnectedComponents {
/** Adjacency-list of original graph */
private int[][] adjListOriginal = null;
/** Adjacency-list of currently viewed subgraph */
private int[][] adjList = null;
/** Helpattribute for finding scc's */
private boolean[] visited = null;
/** Helpattribute for finding scc's */
private Vector stack = null;
/** Helpattribute for finding scc's */
private int[] lowlink = null;
/** Helpattribute for finding scc's */
private int[] number = null;
/** Helpattribute for finding scc's */
private int sccCounter = 0;
/** Helpattribute for finding scc's */
private Vector currentSCCs = null;
/**
* Constructor.
*
* @param adjList adjacency-list of the graph
*/
public StrongConnectedComponents(int[][] adjList) {
this.adjListOriginal = adjList;
}
/**
* This method returns the adjacency-structure of the strong connected
* component with the least vertex in a subgraph of the original graph
* induced by the nodes {s, s + 1, ..., n}, where s is a given node. Note
* that trivial strong connected components with just one node will not
* be returned.
*
* @param node node s
* @return SCCResult with adjacency-structure of the strong
* connected component; null, if no such component exists
*/
public SCCResult getAdjacencyList(int node) {
this.visited = new boolean[this.adjListOriginal.length];
this.lowlink = new int[this.adjListOriginal.length];
this.number = new int[this.adjListOriginal.length];
this.visited = new boolean[this.adjListOriginal.length];
this.stack = new Vector();
this.currentSCCs = new Vector();
this.makeAdjListSubgraph(node);
for (int i = node; i < this.adjListOriginal.length; i++) {
if (!this.visited[i]) {
this.getStrongConnectedComponents(i);
Vector nodes = this.getLowestIdComponent();
if (nodes != null && !nodes.contains(new Integer(node)) && !nodes.contains(new Integer(node + 1))) {
return this.getAdjacencyList(node + 1);
} else {
Vector[] adjacencyList = this.getAdjList(nodes);
if (adjacencyList != null) {
for (int j = 0; j < this.adjListOriginal.length; j++) {
if (adjacencyList[j].size() > 0) {
return new SCCResult(adjacencyList, j);
}
}
}
}
}
}
return null;
}
/**
* Builds the adjacency-list for a subgraph containing just nodes
* >= a given index.
*
* @param node Node with lowest index in the subgraph
*/
private void makeAdjListSubgraph(int node) {
this.adjList = new int[this.adjListOriginal.length][0];
for (int i = node; i < this.adjList.length; i++) {
Vector successors = new Vector();
for (int j = 0; j < this.adjListOriginal[i].length; j++) {
if (this.adjListOriginal[i][j] >= node) {
successors.add(new Integer(this.adjListOriginal[i][j]));
}
}
if (successors.size() > 0) {
this.adjList[i] = new int[successors.size()];
for (int j = 0; j < successors.size(); j++) {
Integer succ = (Integer) successors.get(j);
this.adjList[i][j] = succ.intValue();
}
}
}
}
/**
* Calculates the strong connected component out of a set of scc's, that
* contains the node with the lowest index.
*
* @return Vector::Integer of the scc containing the lowest nodenumber
*/
private Vector getLowestIdComponent() {
int min = this.adjList.length;
Vector currScc = null;
for (int i = 0; i < this.currentSCCs.size(); i++) {
Vector scc = (Vector) this.currentSCCs.get(i);
for (int j = 0; j < scc.size(); j++) {
Integer node = (Integer) scc.get(j);
if (node.intValue() < min) {
currScc = scc;
min = node.intValue();
}
}
}
return currScc;
}
/**
* @return Vector[]::Integer representing the adjacency-structure of the
* strong connected component with least vertex in the currently viewed
* subgraph
*/
private Vector[] getAdjList(Vector nodes) {
Vector[] lowestIdAdjacencyList = null;
if (nodes != null) {
lowestIdAdjacencyList = new Vector[this.adjList.length];
for (int i = 0; i < lowestIdAdjacencyList.length; i++) {
lowestIdAdjacencyList[i] = new Vector();
}
for (int i = 0; i < nodes.size(); i++) {
int node = ((Integer) nodes.get(i)).intValue();
for (int j = 0; j < this.adjList[node].length; j++) {
int succ = this.adjList[node][j];
if (nodes.contains(new Integer(succ))) {
lowestIdAdjacencyList[node].add(new Integer(succ));
}
}
}
}
return lowestIdAdjacencyList;
}
/**
* Searchs for strong connected components reachable from a given node.
*
* @param root node to start from.
*/
private void getStrongConnectedComponents(int root) {
this.sccCounter++;
this.lowlink[root] = this.sccCounter;
this.number[root] = this.sccCounter;
this.visited[root] = true;
this.stack.add(new Integer(root));
for (int i = 0; i < this.adjList[root].length; i++) {
int w = this.adjList[root][i];
if (!this.visited[w]) {
this.getStrongConnectedComponents(w);
this.lowlink[root] = Math.min(lowlink[root], lowlink[w]);
} else if (this.number[w] < this.number[root]) {
if (this.stack.contains(new Integer(w))) {
lowlink[root] = Math.min(this.lowlink[root], this.number[w]);
}
}
}
// found scc
if ((lowlink[root] == number[root]) && (stack.size() > 0)) {
int next = -1;
Vector scc = new Vector();
do {
next = ((Integer) this.stack.get(stack.size() - 1)).intValue();
this.stack.remove(stack.size() - 1);
scc.add(new Integer(next));
} while (this.number[next] > this.number[root]);
// simple scc's with just one node will not be added
if (scc.size() > 1) {
this.currentSCCs.add(scc);
}
}
}
public static void main(String[] args) {
boolean[][] adjMatrix = new boolean[10][];
for (int i = 0; i < 10; i++) {
adjMatrix[i] = new boolean[10];
}
/*adjMatrix[0][1] = true;
adjMatrix[1][2] = true;
adjMatrix[2][0] = true;
adjMatrix[2][4] = true;
adjMatrix[1][3] = true;
adjMatrix[3][6] = true;
adjMatrix[6][5] = true;
adjMatrix[5][3] = true;
adjMatrix[6][7] = true;
adjMatrix[7][8] = true;
adjMatrix[7][9] = true;
adjMatrix[9][6] = true;*/
adjMatrix[0][1] = true;
adjMatrix[1][2] = true;
adjMatrix[2][0] = true; adjMatrix[2][6] = true;
adjMatrix[3][4] = true;
adjMatrix[4][5] = true; adjMatrix[4][6] = true;
adjMatrix[5][3] = true;
adjMatrix[6][7] = true;
adjMatrix[7][8] = true;
adjMatrix[8][6] = true;
adjMatrix[6][1] = true;
int[][] adjList = AdjacencyList.getAdjacencyList(adjMatrix);
StrongConnectedComponents scc = new StrongConnectedComponents(adjList);
for (int i = 0; i < adjList.length; i++) {
System.out.print("i: " + i + "\n");
SCCResult r = scc.getAdjacencyList(i);
if (r != null) {
Vector[] al = scc.getAdjacencyList(i).getAdjList();
for (int j = i; j < al.length; j++) {
if (al[j].size() > 0) {
System.out.print("j: " + j);
for (int k = 0; k < al[j].size(); k++) {
System.out.print(" _" + al[j].get(k).toString());
}
System.out.print("\n");
}
}
System.out.print("\n");
}
}
}
}

View file

@ -0,0 +1,67 @@
package de.normalisiert.utils.graphs;
import java.util.List;
/**
* Testfile for elementary cycle search.
*
* @author Frank Meyer
*
*/
public class TestCycles {
/**
* @param args
*/
public static void main(String[] args) {
String nodes[] = new String[10];
boolean adjMatrix[][] = new boolean[10][10];
for (int i = 0; i < 10; i++) {
nodes[i] = "Node " + i;
}
/*adjMatrix[0][1] = true;
adjMatrix[1][2] = true;
adjMatrix[2][0] = true;
adjMatrix[2][4] = true;
adjMatrix[1][3] = true;
adjMatrix[3][6] = true;
adjMatrix[6][5] = true;
adjMatrix[5][3] = true;
adjMatrix[6][7] = true;
adjMatrix[7][8] = true;
adjMatrix[7][9] = true;
adjMatrix[9][6] = true;*/
adjMatrix[0][1] = true;
adjMatrix[1][2] = true;
adjMatrix[2][0] = true; adjMatrix[2][6] = true;
adjMatrix[3][4] = true;
adjMatrix[4][5] = true; adjMatrix[4][6] = true;
adjMatrix[5][3] = true;
adjMatrix[6][7] = true;
adjMatrix[7][8] = true;
adjMatrix[8][6] = true;
adjMatrix[6][1] = true;
ElementaryCyclesSearch ecs = new ElementaryCyclesSearch(adjMatrix, nodes);
List cycles = ecs.getElementaryCycles();
for (int i = 0; i < cycles.size(); i++) {
List cycle = (List) cycles.get(i);
for (int j = 0; j < cycle.size(); j++) {
String node = (String) cycle.get(j);
if (j < cycle.size() - 1) {
System.out.print(node + " -> ");
} else {
System.out.print(node);
}
}
System.out.print("\n");
}
}
}

View file

@ -0,0 +1,11 @@
(BSD-2 license)
Copyright (c) 2012, Frank Meyer
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.