Traversal and connected components

NetworkX · Practical · 12 min

Reachability and components answer 'what is linked to what', and they're the first step in most analyses.

Breadth-first search (BFS) visits nodes in order of distance from a start; depth-first search (DFS) goes as deep as possible before backtracking. NetworkX exposes both as iterators, so they stay cheap on large graphs:

  • nx.bfs_edges(G, s), nx.bfs_tree(G, s), nx.bfs_layers(G, s) (nodes grouped by distance)
  • nx.dfs_preorder_nodes(G, s), nx.dfs_postorder_nodes, nx.dfs_edges
  • nx.descendants(G, s) and nx.node_connected_component(G, s) for everything reachable

Connected components split an undirected graph into islands: nx.connected_components(G) yields one set of nodes per component, and nx.number_connected_components(G) counts them. The largest is max(nx.connected_components(G), key=len), and G.subgraph(that).copy() extracts it.

Directed graphs have two notions:

  • Strongly connected: every node can reach every other following edge directions (nx.strongly_connected_components). Cycles of mutual reachability, such as link loops.
  • Weakly connected: connected if you ignore direction (nx.weakly_connected_components).

nx.condensation(G) collapses each strongly connected component into one node, producing a DAG of components. That's useful for dependency analysis.

Calling connected_components on a DiGraph raises an error; use the weak or strong version.

Example

import networkx as nx

G = nx.Graph([(1, 2), (2, 3), (3, 4), (2, 5), (10, 11)])
print(list(nx.bfs_layers(G, 1)))           # [[1], [2], [3, 5], [4]]
print(list(nx.dfs_preorder_nodes(G, 1)))   # [1, 2, 3, 4, 5]

comps = sorted(nx.connected_components(G), key=len, reverse=True)
print(comps)                               # [{1, 2, 3, 4, 5}, {10, 11}]
giant = G.subgraph(comps[0]).copy()
print(giant.number_of_nodes())             # 5

D = nx.DiGraph([("a", "b"), ("b", "c"), ("c", "a"), ("c", "d"), ("e", "d")])
print(sorted(map(sorted, nx.strongly_connected_components(D))))
# [['a', 'b', 'c'], ['d'], ['e']]
print(nx.number_weakly_connected_components(D))   # 1
print(nx.condensation(D).number_of_nodes())       # 3  (a DAG of components)

Remember

  • BFS and DFS are lazy iterators; bfs_layers groups by distance
  • Undirected: connected components; directed: strong or weak
  • condensation turns strongly connected components into a DAG

Watch out for

  • Calling nx.connected_components on a DiGraph, which raises NetworkXNotImplemented.

Check yourself

  1. In a DiGraph with edges a->b and b->a plus b->c, which nodes form a strongly connected component of size 2?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: networkx.org/documentation/stable/reference/algorithms/component.html