first commit
This commit is contained in:
commit
d6a0832864
2 changed files with 132 additions and 0 deletions
51
README.md
Normal file
51
README.md
Normal file
|
@ -0,0 +1,51 @@
|
|||
Finding all the elementary circuits of a directed graph
|
||||
-------------------------------------------------------
|
||||
|
||||
Algorithm by D. B. Johnson
|
||||
|
||||
[1] Finding all the elementary circuits of a directed graph.
|
||||
D. B. Johnson, SIAM Journal on Computing 4, no. 1, 77-84, 1975.
|
||||
http://dx.doi.org/10.1137/0204007
|
||||
|
||||
Using the networkx package and a modified version of its simple_cycles()
|
||||
function. The algorithm was adapted so that it would not arbitrarily order
|
||||
vertices. Three lines now contain a sorted() statement.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
python cycles.py 4 0,1 0,2 1,0 1,3 2,0 3,0 3,1 3,2
|
||||
|
||||
First argument is the number of vertices. Subsequent arguments are ordered
|
||||
pairs of comma separated vertices that make up the directed edges of the
|
||||
graph.
|
||||
|
||||
DOT file input
|
||||
--------------
|
||||
|
||||
For simplicity, there is no DOT file parser included but the following allows
|
||||
to create a suitable argument string for simple DOT graphs.
|
||||
|
||||
Given a DOT file of a simple (no labels, colors, styles, only pairs of
|
||||
vertices...) directed graph, the following line produces commandline
|
||||
arguments in the above format for that graph.
|
||||
|
||||
echo `sed -n -e '/^\s*[0-9]\+;$/p' graph.dot | wc -l` `sed -n -e 's/^\s*\([0-9]\) -> \([0-9]\);$/\1,\2/p' graph.dot`
|
||||
|
||||
The above line works on DOT files like the following:
|
||||
|
||||
digraph G {
|
||||
0;
|
||||
1;
|
||||
2;
|
||||
0 -> 1;
|
||||
0 -> 2;
|
||||
1 -> 0;
|
||||
2 -> 0;
|
||||
2 -> 1;
|
||||
}
|
||||
|
||||
It would produce the following output:
|
||||
|
||||
3 0,1 0,2 1,0 2,0 2,1
|
81
cycles.py
Normal file
81
cycles.py
Normal file
|
@ -0,0 +1,81 @@
|
|||
# Copyright (C) 2013 by Johannes Schauer <j.schauer@email.de>
|
||||
#
|
||||
# Copyright (C) 2010 by
|
||||
# Aric Hagberg <hagberg@lanl.gov>
|
||||
# Dan Schult <dschult@colgate.edu>
|
||||
# Pieter Swart <swart@lanl.gov>
|
||||
# All rights reserved.
|
||||
# BSD license.
|
||||
|
||||
|
||||
import sys
|
||||
import networkx as nx
|
||||
from collections import defaultdict
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
print "usage: %s num_vertices [v1,v2...]"%(sys.argv[0])
|
||||
|
||||
def simple_cycles(G):
|
||||
def _unblock(thisnode):
|
||||
"""Recursively unblock and remove nodes from B[thisnode]."""
|
||||
if blocked[thisnode]:
|
||||
blocked[thisnode] = False
|
||||
while B[thisnode]:
|
||||
_unblock(B[thisnode].pop())
|
||||
|
||||
def circuit(thisnode, startnode, component):
|
||||
closed = False # set to True if elementary path is closed
|
||||
path.append(thisnode)
|
||||
blocked[thisnode] = True
|
||||
for nextnode in sorted(component[thisnode]): # direct successors of thisnode
|
||||
if nextnode == startnode:
|
||||
result.append(path + [startnode])
|
||||
closed = True
|
||||
elif not blocked[nextnode]:
|
||||
if circuit(nextnode, startnode, component):
|
||||
closed = True
|
||||
if closed:
|
||||
_unblock(thisnode)
|
||||
else:
|
||||
for nextnode in component[thisnode]:
|
||||
if thisnode not in B[nextnode]: # TODO: use set for speedup?
|
||||
B[nextnode].append(thisnode)
|
||||
path.pop() # remove thisnode from path
|
||||
return closed
|
||||
|
||||
path = [] # stack of nodes in current path
|
||||
blocked = defaultdict(bool) # vertex: blocked from search?
|
||||
B = defaultdict(list) # graph portions that yield no elementary circuit
|
||||
result = [] # list to accumulate the circuits found
|
||||
# Johnson's algorithm requires some ordering of the nodes.
|
||||
# They might not be sortable so we assign an arbitrary ordering.
|
||||
ordering=dict(zip(sorted(G),range(len(G))))
|
||||
for s in sorted(ordering.keys()):
|
||||
# Build the subgraph induced by s and following nodes in the ordering
|
||||
subgraph = G.subgraph(node for node in G
|
||||
if ordering[node] >= ordering[s])
|
||||
# Find the strongly connected component in the subgraph
|
||||
# that contains the least node according to the ordering
|
||||
strongcomp = nx.strongly_connected_components(subgraph)
|
||||
mincomp=min(strongcomp,
|
||||
key=lambda nodes: min(ordering[n] for n in nodes))
|
||||
component = G.subgraph(mincomp)
|
||||
if component:
|
||||
# smallest node in the component according to the ordering
|
||||
startnode = min(component,key=ordering.__getitem__)
|
||||
for node in component:
|
||||
blocked[node] = False
|
||||
B[node][:] = []
|
||||
dummy=circuit(startnode, startnode, component)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
G = nx.DiGraph()
|
||||
|
||||
for edge in sys.argv[2:]:
|
||||
v1,v2 = edge.split(',', 1)
|
||||
G.add_edge(v1,v2)
|
||||
|
||||
for c in simple_cycles(G):
|
||||
print " ".join(c[:-1])
|
Loading…
Reference in a new issue