有向グラフのトポロジのソートを実行するための有向グラフのサンプル実装とサンプルコードを入手できる場所は誰でも知っていますか? (できればJavaで)
トポロジカルソートに関するウィキペディアページ の最初のアルゴリズムの簡単な実装を次に示します。
import Java.util.ArrayList;
import Java.util.Arrays;
import Java.util.HashSet;
import Java.util.Iterator;
public class Graph {
static class Node{
public final String name;
public final HashSet<Edge> inEdges;
public final HashSet<Edge> outEdges;
public Node(String name) {
this.name = name;
inEdges = new HashSet<Edge>();
outEdges = new HashSet<Edge>();
}
public Node addEdge(Node node){
Edge e = new Edge(this, node);
outEdges.add(e);
node.inEdges.add(e);
return this;
}
@Override
public String toString() {
return name;
}
}
static class Edge{
public final Node from;
public final Node to;
public Edge(Node from, Node to) {
this.from = from;
this.to = to;
}
@Override
public boolean equals(Object obj) {
Edge e = (Edge)obj;
return e.from == from && e.to == to;
}
}
public static void main(String[] args) {
Node seven = new Node("7");
Node five = new Node("5");
Node three = new Node("3");
Node eleven = new Node("11");
Node eight = new Node("8");
Node two = new Node("2");
Node nine = new Node("9");
Node ten = new Node("10");
seven.addEdge(eleven).addEdge(eight);
five.addEdge(eleven);
three.addEdge(eight).addEdge(ten);
eleven.addEdge(two).addEdge(nine).addEdge(ten);
eight.addEdge(nine).addEdge(ten);
Node[] allNodes = {seven, five, three, eleven, eight, two, nine, ten};
//L <- Empty list that will contain the sorted elements
ArrayList<Node> L = new ArrayList<Node>();
//S <- Set of all nodes with no incoming edges
HashSet<Node> S = new HashSet<Node>();
for(Node n : allNodes){
if(n.inEdges.size() == 0){
S.add(n);
}
}
//while S is non-empty do
while(!S.isEmpty()){
//remove a node n from S
Node n = S.iterator().next();
S.remove(n);
//insert n into L
L.add(n);
//for each node m with an Edge e from n to m do
for(Iterator<Edge> it = n.outEdges.iterator();it.hasNext();){
//remove Edge e from the graph
Edge e = it.next();
Node m = e.to;
it.remove();//Remove Edge from n
m.inEdges.remove(e);//Remove Edge from m
//if m has no other incoming edges then insert m into S
if(m.inEdges.isEmpty()){
S.add(m);
}
}
}
//Check to see if all edges are removed
boolean cycle = false;
for(Node n : allNodes){
if(!n.inEdges.isEmpty()){
cycle = true;
break;
}
}
if(cycle){
System.out.println("Cycle present, topological sort not possible");
}else{
System.out.println("Topological Sort: "+Arrays.toString(L.toArray()));
}
}
}
ウィキペディアページの2番目の選択肢に基づいて実装したもの: http://en.wikipedia.org/wiki/Topological_sorting
public class Graph {
Hashtable<Node, ArrayList<Node>> adjList = new Hashtable<Node, ArrayList<Node>>();
ArrayList<Node> nodes = new ArrayList<Node>();
LinkedList<Node> topoSorted;
public Graph() {}
public void add(Node node) {
if (adjList.contains(node)) {
return;
} else {
adjList.put(node, new ArrayList<Node>());
nodes.add(node);
}
}
public void addNeighbor(Node from, ArrayList<Node> list) {
for (Node to: list) {
addNeighbor(from, to);
}
}
public void addNeighbor(Node from, Node to) {
if (!adjList.containsKey(from)) {
add(from);
}
if (!adjList.containsKey(to)) {
add(to);
}
adjList.get(from).add(to);
to.inDegree++;
to.inNodes.add(from);
}
public void remove(Node node) {
for (Node n: nodes) {
for (Node x: adjList.get(n)) {
if (x.equals(node)) removeNeighbor(n, x);
}
}
adjList.remove(node);
nodes.remove(node);
}
public void removeNeighbor(Node from, Node to) {
adjList.get(from).remove(to);
to.inDegree--;
to.inNodes.remove(from);
}
public void resetVisited() {
for (Node node: nodes) {
node.visited = false;
}
}
public boolean hasEdge(Node from, Node to) {
return adjList.get(from).contains(to) ? true : false;
}
/**
* for DAGS only
* @throws Exception
*/
public void topologicalSort() throws Exception {
/* L <-- Empty list that will contain the sorted elements */
topoSorted = new LinkedList<Node>();
/* Use set to keep track of permanently visited nodes
* in constant time. Does have pointer overhead */
HashSet<Node> visited = new HashSet<Node>();
/* while there are unmarked nodes do */
for (Node n: nodes) {
/* select an unmarked node n
* visit(n)
*/
if (!visited.contains(n)) visit(n, visited);
}
}
/* function: visit(node n) */
public void visit(Node node, HashSet<Node> set) throws Exception {
/* if n has a temporary mark then stop (not a DAG) */
if (node.visited) {
throw new Exception("graph cyclic");
/* if n is not marked (i.e. has not been visited) then... */
} else {
/* mark n temporarily [using boolean field in node]*/
node.visited = true;
/* for each node m with an Edge n to m do... */
for (Node m: adjList.get(node)) {
/* visit(m) */
if (!set.contains(m)) visit(m, set);
}
/* mark n permanently */
set.add(node);
/* unmark n temporarily */
node.visited = false;
/* add n to head of L */
topoSorted.addFirst(node);
}
}
public void printGraph() {
for (Node node: nodes) {
System.out.print("from: " + node.value + " | to: ");
for (Node m: adjList.get(node)) {
System.out.print(m.value + " ");
}
System.out.println();
}
}
public void instantiateGraph() {
Node seven = new Node("7");
Node five = new Node("5");
Node three = new Node("3");
Node eleven = new Node("11");
Node eight = new Node("8");
Node two = new Node("2");
Node nine = new Node("9");
Node ten = new Node("10");
addNeighbor(seven, eleven);
addNeighbor(seven, eight);
addNeighbor(five, eleven);
addNeighbor(three, eight);
addNeighbor(three, ten);
addNeighbor(eleven, two);
addNeighbor(eleven, nine);
addNeighbor(eleven, ten);
addNeighbor(eight, nine);
try {
topologicalSort();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for (Node node: topoSorted) {
System.out.print(node.value + " ");
}
}
public class Node {
String value;
boolean visited = false;
int inDegree = 0;
ArrayList<Node> inNodes = new ArrayList<Node>();
public Node (String value) {
this.value = value;
}
}
public static void main(String[] args) {
Graph g = new Graph();
g.instantiateGraph();
}
}
JGraphT などのサードパーティのオープンソースプロジェクトを使用することもできます。多くのシンプルで複雑なグラフ構造とその視覚的表現を提供します。また、この方法で構造的な問題に対処する必要はありません。
HashSet
をエッジで使用しているため、hashCode()
関数もオーバーライドする必要があります。
そうしないと、予期しないバグが発生します。
EXP:同じfromとtoを持つ2つのインスタンスをhashset
に追加します。 2番目のものは、上書きされることになっているhashCode()
なしでは上書きされません。
@templatetypedefで優れたソリューションを少しだけ強化するために、ユニットテストをいくつか追加して、自分や他の人が使用する自信を高めました。お役に立てれば...
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import Java.util.List;
import org.junit.Test;
public class TestTopologicalSort {
@Test (expected=Java.lang.NullPointerException.class)
public void testNullGraph() {
final List<String> orderedLayers = TopologicalSort.sort(null);
}
@Test
public void testEmptyGraph() {
final DirectedGraph<String> dag = new DirectedGraph<String>();
final List<String> orderedLayers = TopologicalSort.sort(dag);
assertEquals(0, orderedLayers.size());
}
@Test
public void testSingleVertex() {
final DirectedGraph<String> dag = new DirectedGraph<String>();
dag.addNode("a");
final List<String> orderedLayers = TopologicalSort.sort(dag);
assertEquals(1, orderedLayers.size());
assertTrue(orderedLayers.contains("a"));
}
@Test
public void testMultipleVertices() {
final DirectedGraph<String> dag = new DirectedGraph<String>();
dag.addNode("a");
dag.addNode("b");
final List<String> orderedLayers = TopologicalSort.sort(dag);
assertEquals(2, orderedLayers.size());
assertTrue(orderedLayers.contains("a"));
assertTrue(orderedLayers.contains("b"));
}
@Test (expected=Java.util.NoSuchElementException.class)
public void testBogusEdge() {
final DirectedGraph<String> dag = new DirectedGraph<String>();
dag.addNode("a");
dag.addEdge("a", "b");
}
@Test
public void testSimpleDag() {
final DirectedGraph<String> dag = new DirectedGraph<String>();
dag.addNode("b");
dag.addNode("a");
dag.addEdge("a", "b");
final List<String> orderedLayers = TopologicalSort.sort(dag);
assertEquals(2, orderedLayers.size());
assertTrue(orderedLayers.get(0).equals("a"));
assertTrue(orderedLayers.get(1).equals("b"));
}
@Test
public void testComplexGraph() {
// node b has two incoming edges
final DirectedGraph<String> dag = new DirectedGraph<String>();
dag.addNode("a");
dag.addNode("b");
dag.addNode("c");
dag.addNode("d");
dag.addNode("e");
dag.addNode("f");
dag.addNode("g");
dag.addNode("h");
dag.addEdge("a", "b");
dag.addEdge("a", "c");
dag.addEdge("c", "d");
dag.addEdge("d", "b");
dag.addEdge("c", "e");
dag.addEdge("f", "g");
final List<String> orderedLayers = TopologicalSort.sort(dag);
assertEquals(8, orderedLayers.size());
assertTrue(orderedLayers.indexOf("a") < orderedLayers.indexOf("b"));
assertTrue(orderedLayers.indexOf("a") < orderedLayers.indexOf("c"));
assertTrue(orderedLayers.indexOf("c") < orderedLayers.indexOf("d"));
assertTrue(orderedLayers.indexOf("c") < orderedLayers.indexOf("e"));
assertTrue(orderedLayers.indexOf("d") < orderedLayers.indexOf("b"));
assertTrue(orderedLayers.indexOf("f") < orderedLayers.indexOf("g"));
}
@Test (expected=Java.lang.IllegalArgumentException.class)
public void testCycle() {
// cycle between a, c, and d
final DirectedGraph<String> dag = new DirectedGraph<String>();
dag.addNode("a");
dag.addNode("b");
dag.addNode("c");
dag.addNode("d");
dag.addNode("e");
dag.addNode("f");
dag.addNode("g");
dag.addNode("h");
dag.addEdge("a", "b");
dag.addEdge("a", "c");
dag.addEdge("c", "d");
dag.addEdge("d", "a");
dag.addEdge("c", "e");
dag.addEdge("f", "g");
final List<String> orderedLayers = TopologicalSort.sort(dag);
}
}
ジェレミーに同意します。
効果的なJavaに基づいてハッシュコードを取得するための実装がここにあると思います: http://www.javapractices.com/topic/TopicAction.do?Id=28
ハッシュコードをオーバーライドする以下のメソッドを追加するにはどうすればよいですか?
@Override
public int hashCode(){
if (fHashCode == 0) {
int result = HashCodeUtil.SEED;
result = HashCodeUtil.hash(result, from);
result = HashCodeUtil.hash(result, to);
}
return fHashCode;
}
少し前に行った実装を次に示します。
_/**
*
* Sorts a directed graph, obtaining a visiting sequence ("sorted" list)
* that respects the "Predecessors" (as in a job/task requirements list).
* (when there is freedom, the original ordering is preferred)
*
* The behaviour in case of loops (cycles) depends on the "mode":
* permitLoops == false : loops are detected, but result is UNDEFINED (simpler)
* permitLoops == true : loops are detected, result a "best effort" try, original ordering is privileged
*
* http://en.wikipedia.org/wiki/Topological_sort
*/
public class TopologicalSorter<T extends DirectedGraphNode> {
private final boolean permitLoops;
private final Collection<T> graph; // original graph. this is not touched.
private final List<T> sorted = new ArrayList<T>(); // result
private final Set<T> visited = new HashSet<T>(); // auxiliar list
private final Set<T> withLoops = new HashSet<T>();
// auxiliar: all succesors (also remote) of each node; this is only used if permitLoops==true
private HashMap<T, Set<T>> succesors = null;
public TopologicalSorter(Collection<T> graph, boolean permitLoops) {
this.graph = graph;
this.permitLoops = permitLoops;
}
public void sort() {
init();
for( T n : graph ) {
if( permitLoops ) visitLoopsPermitted(n);
else visitLoopsNoPermitted(n, new HashSet<T>());
}
}
private void init() {
sorted.clear();
visited.clear();
withLoops.clear();
// build succesors map: only it permitLoops == true
if( permitLoops ) {
succesors = new HashMap<T, Set<T>>();
HashMap<T, Set<T>> addTo = new HashMap();
for( T n : graph ) {
succesors.put(n, new HashSet<T>());
addTo.put(n, new HashSet<T>());
}
for( T n2 : graph ) {
for( DirectedGraphNode n1 : n2.getPredecessors() ) {
succesors.get(n1).add(n2);
}
}
boolean change = false;
do {
change = false;
for( T n : graph ) {
addTo.get(n).clear();
for( T ns : succesors.get(n) ) {
for( T ns2 : succesors.get(ns) ) {
if( !succesors.get(n).contains(ns2) ) {
change = true;
addTo.get(n).add(ns2);
}
}
}
}
for( DirectedGraphNode n : graph ) {
succesors.get(n).addAll(addTo.get(n));
}
} while(change);
}
}
private void visitLoopsNoPermitted(T n, Set<T> visitedInThisCallStack) { // this is simpler than visitLoopsPermitted
if( visited.contains(n) ) {
if( visitedInThisCallStack.contains(n) ) {
withLoops.add(n); // loop!
}
return;
}
//System.out.println("visiting " + n.toString());
visited.add(n);
visitedInThisCallStack.add(n);
for( DirectedGraphNode n1 : n.getPredecessors() ) {
visitLoopsNoPermitted((T) n1, visitedInThisCallStack);
}
sorted.add(n);
}
private void visitLoopsPermitted(T n) {
if( visited.contains(n) ) return;
//System.out.println("visiting " + n.toString());
visited.add(n);
for( DirectedGraphNode n1 : n.getPredecessors() ) {
if( succesors.get(n).contains(n1) ) {
withLoops.add(n);
withLoops.add((T) n1);
continue;
} // loop!
visitLoopsPermitted((T) n1);
}
sorted.add(n);
}
public boolean hadLoops() {
return withLoops.size() > 0;
}
public List<T> getSorted() {
return sorted;
}
public Set<T> getWithLoops() {
return withLoops;
}
public void showResult() { // for debugging
for( DirectedGraphNode node : sorted ) {
System.out.println(node.toString());
}
if( hadLoops() ) {
System.out.println("LOOPS!:");
for( DirectedGraphNode node : withLoops ) {
System.out.println(" " + node.toString());
}
}
}
}
/**
* Node that conform a DirectedGraph
* It is used by TopologicalSorter
*/
public interface DirectedGraphNode {
/**
* empty collection if no predecessors
* @return
*/
public Collection<DirectedGraphNode> getPredecessors();
}
_
そして、ここで使用の一例:
_public class TopologicalSorterExample {
public static class Node implements DirectedGraphNode {
public final String x;
public ArrayList<DirectedGraphNode> antec = new ArrayList<DirectedGraphNode>(); // immediate antecesors
public Node(String x) {this.x= x;}
public Collection<DirectedGraphNode> getPredecessors() {
return antec;
}
public String toString() {
return x;
}
}
public static void main(String[] args) {
List<DirectedGraphNode> graph = new ArrayList<DirectedGraphNode>();
Node na = new Node("A");
Node nb = new Node("B");
Node nc = new Node("C");
Node nd = new Node("D");
Node ne = new Node("E");
nc.antec.add(na);
nc.antec.add(nb);
nd.antec.add(ne);
ne.antec.add(na);
na.antec.add(nd);
graph.add(nc);
graph.add(na);
graph.add(nb);
graph.add(ne);
graph.add(nd);
TopologicalSorter ts = new TopologicalSorter(graph, false);
ts.sort();
ts.showResult();
}
}
_
コードに2つの追加機能(または複雑化):私の場合、ループ(サイクル)をサポートする必要がありました。そのため、グラフにループがある場合は、「ベストエフォート」の順序になります。この動作は、コンストラクターに渡されるフラグによって制御されます。いずれの場合でも、hadLoops()
を呼び出して(検出すべき)サイクルが検出されたかどうかを確認できます。それに、自由のために、ソートアルゴリズムが元の順序を優先するようにしたかったのです。