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.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值