1

I need to find the connected components of a graph. I have a neighbour list of nodes:

neighbour_list = [[4], [2, 5], [1, 3, 8], [2, 14], [0, 9], [1], [10, 12], [13], [2, 14], [4, 10, 15], [6, 9], [17], [6], [7, 19, 20], [3, 8], [9, 21], [22], [11, 18], [17, 19], [13, 18, 26], [13, 26], [15, 27], [16, 23], [22, 24, 28], [23, 25, 29], [24], [19, 20, 30, 31], [21], [23, 29], [24, 28], [26, 31], [26, 30]]

For example node 0 has neighbour 4, node 1 has neighbours 2 and 5 etc... What I want to find is a list of connected components. Say node 0 has neighbour 4, yet neighbour 4 also is a neighbour of node 9. Node 9 also has number 10 and 15 neighbours. So the list would be something like

[4,10,15....] etc including following neihbours.

the method I am trying to use is breadth first search. I wrote the following algorithm:

 def bfs(neighbour_list, node):

    label_list =[]   
    for sub_neighbour_list in neighbour_list: 

      label_list.append(node)

      queue = [node]

    while queue:
        u = queue[0]
        for sub_neighbour in neighbour_list[u]:
           if sub_neighbour not in queue:
             label_list[sub_neighbour] = 0
             queue.append(sub_neighbour)
             queue.pop(0)

   print(label_list)
   return (label_list)

nothing happens when I run it. What is wrong?

thanks

jchanger
  • 739
  • 10
  • 29
Hannah
  • 137
  • 1
  • 3
  • 12
  • Indent the three lines following the "if" statement, and all of the lines after the first "for" up to but not including the "print" statement. – Marichyasana Oct 01 '14 at 23:49
  • @Marichyasana still doesnt do anything – Hannah Oct 01 '14 at 23:52
  • Where's `sub_neighbour_list` being defined? – jedwards Oct 02 '14 at 00:01
  • 1
    Can you show us what parameters you're calling it with, and update the formatting of the originsal question code? – Tom Dalton Oct 02 '14 at 00:01
  • You pass in a parameter `node` but then your line `for node in sub_neighbour_list in neighbour_list:` redefines `node`. So you lose the original node you were looking for. – Tom Dalton Oct 02 '14 at 00:04
  • I'd also expect `label_list = []` followed by `label_list[node] = 0` would give an `IndexError`... – Tom Dalton Oct 02 '14 at 00:06
  • `queue.pop(0)` after `u = queue[0]`, inside the for loop that reads every neighbors of an unvisited node `queue.pop[0]` will drop all the nodes – Yossarian42 Jul 03 '19 at 11:54
  • In addition to jedwards' answer I would suggest to use the data structure deque `from collections import deque` and then `queue = deque()` and `queue.popleft()`. This should speed up the performance. – sono May 04 '17 at 09:09

2 Answers2

3

What about:

neighbour_list = [[4], [2, 5], [1, 3, 8], [2, 14], [0, 9], [1], [10, 12], [13], 
                 [2, 14], [4, 10, 15], [6, 9], [17], [6], [7, 19, 20], [3, 8], 
                 [9, 21], [22], [11, 18], [17, 19], [13, 18, 26], [13, 26], 
                 [15, 27], [16, 23], [22, 24, 28], [23, 25, 29], [24], 
                 [19, 20, 30, 31], [21], [23, 29], [24, 28], [26, 31], [26, 30]]

def bfs(neighbour_list, root):
    queue = []
    seen = set()

    queue.append(root)
    seen.add(root)

    while queue:
        cn = queue.pop(0)
        print("Current node: %d" % cn)
        for nn in neighbour_list[cn]:
            if nn not in seen:
                queue.append(nn)
                seen.add(nn)
                print("  Found %d" % nn)

    return seen

print bfs(neighbour_list, 0)

Which outputs:

Current node: 0
  Found 4
Current node: 4
  Found 9
Current node: 9
  Found 10
  Found 15
Current node: 10
  Found 6
Current node: 15
  Found 21
Current node: 6
  Found 12
Current node: 21
  Found 27
Current node: 12
Current node: 27
set([0, 4, 6, 9, 10, 12, 15, 21, 27])

Note that set isn't ordered. So the result of this function will return all nodes reachable by root, but not in any sort of order of when the algorithm reached it. If you want that, you could easily change seen to be a list.

jedwards
  • 29,432
  • 3
  • 65
  • 92
  • what is the defference betwwen .append and .add ? @jedwards you append the list but add to the dictionnary? – Hannah Oct 02 '14 at 00:15
  • You `.append()` to a list and `.add()` to a set. A set is an unordered collection whereas a list is ordered. – Tom Dalton Oct 02 '14 at 00:16
  • and whats the difference between queue.pop(0) and queue[0] dont they have the same result? @Tom Dalton – Hannah Oct 02 '14 at 00:19
  • Sorry for the delay, `queue.pop(0)` returns the first element (same as `queue[0]`, but `pop` removes that element as well, simultaneously. – jedwards Oct 02 '14 at 01:14
  • What's the point in doing expensive `pop(0)` instead of just `pop()` if you still return the component as an (unordered) set? – Eugene Yarmash Oct 04 '19 at 19:58
1

This generator function will yield all connected components in a graph represented as an adjacency list. It also uses collections.deque for a queue instead of a list for efficient pops from the beginning of the queue.

from collections import deque

def connected_components(graph):
    seen = set()

    for root in range(len(graph)):
        if root not in seen:
            seen.add(root)

            component = []
            queue = deque([root])

            while queue:
                node = queue.popleft()
                component.append(node)
                for neighbor in graph[node]:
                    if neighbor not in seen:
                        seen.add(neighbor)
                        queue.append(neighbor)
            yield component

Demo:

neighbour_list = [[4], [2, 5], [1, 3, 8], [2, 14], [0, 9], [1], [10, 12], [13],
                  [2, 14], [4, 10, 15], [6, 9], [17], [6], [7, 19, 20], [3, 8],
                  [9, 21], [22], [11, 18], [17, 19], [13, 18, 26], [13, 26],
                  [15, 27], [16, 23], [22, 24, 28], [23, 25, 29], [24],
                  [19, 20, 30, 31], [21], [23, 29], [24, 28], [26, 31], [26, 30]]

print(list(connected_components(neighbour_list)))
# [[0, 4, 9, 10, 15, 6, 21, 12, 27],
#  [1, 2, 5, 3, 8, 14],
#  [7, 13, 19, 20, 18, 26, 17, 30, 31, 11],
#  [16, 22, 23, 24, 28, 25, 29]]
Eugene Yarmash
  • 142,882
  • 41
  • 325
  • 378