0

I'm trying to find cycles in an undirected, unweighted graph. In [node, node] format. Here is the code I wrote:

def find_cycles(graph):
    cycles = []

    def dfs(node, visited, path):
        visited.add(node)
        path.append(node)

        neighbors = graph.get(node, [])

        for neighbor in neighbors:
            if neighbor in visited:
                # Cycle detected
                start_index = path.index(neighbor)
                cycle = path[start_index:]
                if cycle not in cycles:
                    cycles.append(cycle)
            else:
                dfs(neighbor, visited, path)

        visited.remove(node)
        path.pop()

    for node in graph.keys():
        dfs(node, set(), [])

    return cycles

graph = {
    # 'node': ['adjacent'],
}

n, m = map(int, input().split())

for _ in range(m):
    a, b = map(int, input().split())
    if b not in graph:
        graph[b] = []
    if a not in graph:
        graph[a] = []

    if a not in graph[b]:
        graph[b].append(a)
    if b not in graph[a]:
        graph[a].append(b)

ans = find_cycles(graph)
print(ans)
print(len(ans))

In the test case:

10 10
3 6
9 3
1 7
1 2
4 7
7 6
2 9
2 6
3 4
6 0

I know that the shortest cycle length is 4, but it prints a wrong list containing 92 items, with the shortest one being of length 2. What is wrong in my code?

mkrieger1
  • 19,194
  • 5
  • 54
  • 65
Mani
  • 3
  • 2
  • Why is [2,6,0,2] valid? I don't see any connection between 0 and 2. 0 only connects to 6. And if it is undirected. why wouldn't the list be [3,6,3], [9,3,9], [1,7,1], etc.? And this is not really a graph, because 10 sits by itself, not connected to anything. – Tim Roberts Aug 21 '23 at 20:50
  • You're right @TimRoberts I made a mistake. It's generated by ChatGPT and I didn't bother to check in the middle of debugging. My bad. – Mani Aug 21 '23 at 20:55
  • OK, if I draw this by hand, I do get 8 cycles of 5 or longer (assuming we don't count the over-and-back cycles like 3-6-3 and 9-3-9). It's shaped like an envelope with 4 at the peak, 1-2-9 at the base, 7-6-3 in the middle. – Tim Roberts Aug 21 '23 at 21:00
  • @TimRoberts I guess you're counting the nodes in the cycles. If we count the edges, the minimum is 4. Am I right? – Mani Aug 21 '23 at 21:04
  • Exactly right. The problem with your code is that you are counting rotations of the same path multiple times. You have the 1-7-4-3-9-2 cycle, but you count it starting at 1, and starting at 7, and starting at 4, etc. Perhaps you need to sort the numbers to check for uniqueness. Also, you should throw out any cycle of length 2. – Tim Roberts Aug 21 '23 at 21:06
  • Oh, now I get it. Thanks you @TimRoberts you've been such a nice man. – Mani Aug 21 '23 at 21:21

1 Answers1

0

I've modified your code very slightly. Instead of checking if cycle in cycles, I'm maintaining a set of the paths we have seen, with the nodes in sorted order. If the new cycle is not present in the set, then I add it to the list of cycles. I also discard any cycle with only two edges.

With this, I get 7 cycles, and I think it is correct.

data = """10 10
3 6
9 3
1 7
1 2
4 7
7 6
2 9
2 6
3 4
6 0""".splitlines()

def find_cycles(graph):
    cycles = []
    checked = set()

    def dfs(node, visited, path):
        visited.add(node)
        path.append(node)

        neighbors = graph.get(node, [])

        for neighbor in neighbors:
            if neighbor in visited:
                # Cycle detected
                start_index = path.index(neighbor)
                cycle = path[start_index:]
                m = tuple(sorted(cycle))
                if len(cycle) > 2 and m not in checked:
                    checked.add(m)
                    cycles.append(cycle)
            else:
                dfs(neighbor, visited, path)

        visited.remove(node)
        path.pop()

    for node in graph.keys():
        dfs(node, set(), [])

    return cycles

graph = {
    # 'node': ['adjacent'],
}

n, m = map(int, data.pop(0).split())

for _ in range(m):
    a, b = map(int, data.pop(0).split())
    if b not in graph:
        graph[b] = []
    if a not in graph:
        graph[a] = []

    if a not in graph[b]:
        graph[b].append(a)
    if b not in graph[a]:
        graph[a].append(b)

ans = find_cycles(graph)
print(ans)
print(len(ans))

Output:

[[3, 9, 2, 1, 7, 4], [6, 3, 9, 2, 1, 7], [6, 3, 9, 2], [6, 3, 4, 7, 1, 2], [6, 3, 4, 7], [6, 7, 1, 2], [6, 7, 4, 3, 9, 2]]
7
Tim Roberts
  • 48,973
  • 4
  • 21
  • 30