Finding all cycles in undirected graphs

I need a working algorithm for finding all simple cycles in an undirected graph. I know the cost can be exponential and the problem is NP-complete, but I am going to use it in a small graph (up to 20-30 vertices) and the cycles are small in number.

The following is a demo implementation in Python based on depth first search.
An outer loop scans all nodes of the graph and starts a search from every node. Node neighbours (according to the list of edges) are added to the cycle path. Recursion ends if no more non-visited neighbours can be added. A new cycle is found if the path is longer than two nodes and the next neighbour is the start of the path. To avoid duplicate cycles, the cycles are normalized by rotating the smallest node to the start. Cycles in inverted ordering are also taken into account.
This is just a naive implementation. The classical paper is: Donald B. Johnson. Finding all the elementary circuits of a directed graph. SIAM J. Comput., 4(1):77–84, 1975.

Python

graph = [[1, 3], [1, 4], [1, 5],[1,6], [2, 3], 
         [2, 4],[2, 5], [2, 6], [3, 5], [3,6],
         [4, 5], [4, 6]]

cycles = []
def main():
    global graph
    global cycles
    for edge in graph:
        for node in edge:
            findNewCycles([node])
    for cy in cycles:
        path = [str(node) for node in cy]
        s = ",".join(path)
        print(s)

def findNewCycles(path):
    start_node = path[0]
    next_node= None
    sub = []

    #visit each edge and each node of each edge
    for edge in graph:
        node1, node2 = edge
        if start_node in edge:
                if node1 == start_node:
                    next_node = node2
                else:
                    next_node = node1
                if not visited(next_node, path):
                        # neighbor node not on path yet
                        sub = [next_node]
                        sub.extend(path)
                        # explore extended path
                        findNewCycles(sub);
                elif len(path) > 2  and next_node == path[-1]:
                        # cycle found
                        p = rotate_to_smallest(path);
                        inv = invert(p)
                        if isNew(p) and isNew(inv):
                            cycles.append(p)

def invert(path):
    return rotate_to_smallest(path[::-1])

#  rotate cycle path such that it begins with the smallest node
def rotate_to_smallest(path):
    n = path.index(min(path))
    return path[n:]+path[:n]

def isNew(path):
    return not path in cycles

def visited(node, path):
    return node in path

main()
print(len(cycles))
1,4,2,3
1,5,4,2,3
1,6,4,2,3
1,5,2,3
1,4,5,2,3
1,6,4,5,2,3
1,6,2,3
1,4,6,2,3
1,5,4,6,2,3
1,5,3
1,4,2,5,3
1,6,4,2,5,3
1,6,2,5,3
1,4,6,2,5,3
1,4,5,3
1,6,2,4,5,3
1,6,4,5,3
1,6,3
1,4,2,6,3
1,5,4,2,6,3
1,5,2,6,3
1,4,5,2,6,3
1,4,6,3
1,5,2,4,6,3
1,5,4,6,3
1,5,3,2,4
1,6,3,2,4
1,5,2,4
1,6,3,5,2,4
1,6,2,4
1,5,3,6,2,4
1,5,4
1,6,3,2,5,4
1,6,2,5,4
1,6,2,3,5,4
1,6,3,5,4
1,6,4
1,5,3,2,6,4
1,5,2,6,4
1,5,2,3,6,4
1,5,3,6,4
1,6,3,2,5
1,6,4,2,5
1,6,2,5
1,6,4,2,3,5
1,6,2,3,5
1,6,3,5
1,6,3,2,4,5
1,6,2,4,5
1,6,4,5
2,3,5,4
2,3,6,4
2,3,5
2,3,6,4,5
2,3,6
2,3,5,4,6
2,5,3,6,4
2,5,3,6
2,4,5,3,6
3,6,4,5
2,4,5
2,4,6
2,5,4,6
63

矩阵方法 Maple
Obviously the number of cycles of length k is closely related to the number of walks of length k that end at their own starting point. Clearly, this number of walks is the trace of the k t h k^{th} kth power of the adjacency matrix. It only remains to remove the walks that aren’t cycles and to divide out the 2*k permutations of each cycle. This formula does that:

#Number of k-cycles for a given adjacency matrix A
kCycles:= (A::Matrix, n::posint, k::And(posint, Not({1,2})))->
   add(
      (-1)^(k-i)*binomial(n-i,n-k) * 
         add(LinearAlgebra:-Trace(A[S,S]^k), S= combinat:-choose(n,i)), 
      i= 2..k
   )/2/k
:
A:= GraphTheory:-AdjacencyMatrix(GraphTheory:-SpecialGraphs:-OctahedronGraph()):
n:= 6: #number of vertices 
seq(kCycles(A, n, k), k= 3..6);
                         8, 15, 24, 16
add([%]); #total number of cycles
                               63

These results agree with VV’s explicit list of the cycles of this graph.
My little procedure above will gain a huge efficiency improvement by using remember tables for the traces, but I don’t have time right now. If someone else chooses to undertake this, recall that a remember table cannot be indexed correctly by a mutable structure like a Matrix. So, use S and k as the table indices.

你可能感兴趣的:(图论,算法)