strongly_connected.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. """Strongly connected components."""
  2. import networkx as nx
  3. from networkx.utils.decorators import not_implemented_for
  4. __all__ = [
  5. "number_strongly_connected_components",
  6. "strongly_connected_components",
  7. "is_strongly_connected",
  8. "strongly_connected_components_recursive",
  9. "kosaraju_strongly_connected_components",
  10. "condensation",
  11. ]
  12. @nx._dispatch
  13. @not_implemented_for("undirected")
  14. def strongly_connected_components(G):
  15. """Generate nodes in strongly connected components of graph.
  16. Parameters
  17. ----------
  18. G : NetworkX Graph
  19. A directed graph.
  20. Returns
  21. -------
  22. comp : generator of sets
  23. A generator of sets of nodes, one for each strongly connected
  24. component of G.
  25. Raises
  26. ------
  27. NetworkXNotImplemented
  28. If G is undirected.
  29. Examples
  30. --------
  31. Generate a sorted list of strongly connected components, largest first.
  32. >>> G = nx.cycle_graph(4, create_using=nx.DiGraph())
  33. >>> nx.add_cycle(G, [10, 11, 12])
  34. >>> [
  35. ... len(c)
  36. ... for c in sorted(nx.strongly_connected_components(G), key=len, reverse=True)
  37. ... ]
  38. [4, 3]
  39. If you only want the largest component, it's more efficient to
  40. use max instead of sort.
  41. >>> largest = max(nx.strongly_connected_components(G), key=len)
  42. See Also
  43. --------
  44. connected_components
  45. weakly_connected_components
  46. kosaraju_strongly_connected_components
  47. Notes
  48. -----
  49. Uses Tarjan's algorithm[1]_ with Nuutila's modifications[2]_.
  50. Nonrecursive version of algorithm.
  51. References
  52. ----------
  53. .. [1] Depth-first search and linear graph algorithms, R. Tarjan
  54. SIAM Journal of Computing 1(2):146-160, (1972).
  55. .. [2] On finding the strongly connected components in a directed graph.
  56. E. Nuutila and E. Soisalon-Soinen
  57. Information Processing Letters 49(1): 9-14, (1994)..
  58. """
  59. preorder = {}
  60. lowlink = {}
  61. scc_found = set()
  62. scc_queue = []
  63. i = 0 # Preorder counter
  64. neighbors = {v: iter(G[v]) for v in G}
  65. for source in G:
  66. if source not in scc_found:
  67. queue = [source]
  68. while queue:
  69. v = queue[-1]
  70. if v not in preorder:
  71. i = i + 1
  72. preorder[v] = i
  73. done = True
  74. for w in neighbors[v]:
  75. if w not in preorder:
  76. queue.append(w)
  77. done = False
  78. break
  79. if done:
  80. lowlink[v] = preorder[v]
  81. for w in G[v]:
  82. if w not in scc_found:
  83. if preorder[w] > preorder[v]:
  84. lowlink[v] = min([lowlink[v], lowlink[w]])
  85. else:
  86. lowlink[v] = min([lowlink[v], preorder[w]])
  87. queue.pop()
  88. if lowlink[v] == preorder[v]:
  89. scc = {v}
  90. while scc_queue and preorder[scc_queue[-1]] > preorder[v]:
  91. k = scc_queue.pop()
  92. scc.add(k)
  93. scc_found.update(scc)
  94. yield scc
  95. else:
  96. scc_queue.append(v)
  97. @not_implemented_for("undirected")
  98. def kosaraju_strongly_connected_components(G, source=None):
  99. """Generate nodes in strongly connected components of graph.
  100. Parameters
  101. ----------
  102. G : NetworkX Graph
  103. A directed graph.
  104. Returns
  105. -------
  106. comp : generator of sets
  107. A generator of sets of nodes, one for each strongly connected
  108. component of G.
  109. Raises
  110. ------
  111. NetworkXNotImplemented
  112. If G is undirected.
  113. Examples
  114. --------
  115. Generate a sorted list of strongly connected components, largest first.
  116. >>> G = nx.cycle_graph(4, create_using=nx.DiGraph())
  117. >>> nx.add_cycle(G, [10, 11, 12])
  118. >>> [
  119. ... len(c)
  120. ... for c in sorted(
  121. ... nx.kosaraju_strongly_connected_components(G), key=len, reverse=True
  122. ... )
  123. ... ]
  124. [4, 3]
  125. If you only want the largest component, it's more efficient to
  126. use max instead of sort.
  127. >>> largest = max(nx.kosaraju_strongly_connected_components(G), key=len)
  128. See Also
  129. --------
  130. strongly_connected_components
  131. Notes
  132. -----
  133. Uses Kosaraju's algorithm.
  134. """
  135. post = list(nx.dfs_postorder_nodes(G.reverse(copy=False), source=source))
  136. seen = set()
  137. while post:
  138. r = post.pop()
  139. if r in seen:
  140. continue
  141. c = nx.dfs_preorder_nodes(G, r)
  142. new = {v for v in c if v not in seen}
  143. seen.update(new)
  144. yield new
  145. @not_implemented_for("undirected")
  146. def strongly_connected_components_recursive(G):
  147. """Generate nodes in strongly connected components of graph.
  148. Recursive version of algorithm.
  149. Parameters
  150. ----------
  151. G : NetworkX Graph
  152. A directed graph.
  153. Returns
  154. -------
  155. comp : generator of sets
  156. A generator of sets of nodes, one for each strongly connected
  157. component of G.
  158. Raises
  159. ------
  160. NetworkXNotImplemented
  161. If G is undirected.
  162. Examples
  163. --------
  164. Generate a sorted list of strongly connected components, largest first.
  165. >>> G = nx.cycle_graph(4, create_using=nx.DiGraph())
  166. >>> nx.add_cycle(G, [10, 11, 12])
  167. >>> [
  168. ... len(c)
  169. ... for c in sorted(
  170. ... nx.strongly_connected_components_recursive(G), key=len, reverse=True
  171. ... )
  172. ... ]
  173. [4, 3]
  174. If you only want the largest component, it's more efficient to
  175. use max instead of sort.
  176. >>> largest = max(nx.strongly_connected_components_recursive(G), key=len)
  177. To create the induced subgraph of the components use:
  178. >>> S = [G.subgraph(c).copy() for c in nx.weakly_connected_components(G)]
  179. See Also
  180. --------
  181. connected_components
  182. Notes
  183. -----
  184. Uses Tarjan's algorithm[1]_ with Nuutila's modifications[2]_.
  185. References
  186. ----------
  187. .. [1] Depth-first search and linear graph algorithms, R. Tarjan
  188. SIAM Journal of Computing 1(2):146-160, (1972).
  189. .. [2] On finding the strongly connected components in a directed graph.
  190. E. Nuutila and E. Soisalon-Soinen
  191. Information Processing Letters 49(1): 9-14, (1994)..
  192. """
  193. def visit(v, cnt):
  194. root[v] = cnt
  195. visited[v] = cnt
  196. cnt += 1
  197. stack.append(v)
  198. for w in G[v]:
  199. if w not in visited:
  200. yield from visit(w, cnt)
  201. if w not in component:
  202. root[v] = min(root[v], root[w])
  203. if root[v] == visited[v]:
  204. component[v] = root[v]
  205. tmpc = {v} # hold nodes in this component
  206. while stack[-1] != v:
  207. w = stack.pop()
  208. component[w] = root[v]
  209. tmpc.add(w)
  210. stack.remove(v)
  211. yield tmpc
  212. visited = {}
  213. component = {}
  214. root = {}
  215. cnt = 0
  216. stack = []
  217. for source in G:
  218. if source not in visited:
  219. yield from visit(source, cnt)
  220. @not_implemented_for("undirected")
  221. def number_strongly_connected_components(G):
  222. """Returns number of strongly connected components in graph.
  223. Parameters
  224. ----------
  225. G : NetworkX graph
  226. A directed graph.
  227. Returns
  228. -------
  229. n : integer
  230. Number of strongly connected components
  231. Raises
  232. ------
  233. NetworkXNotImplemented
  234. If G is undirected.
  235. Examples
  236. --------
  237. >>> G = nx.DiGraph([(0, 1), (1, 2), (2, 0), (2, 3), (4, 5), (3, 4), (5, 6), (6, 3), (6, 7)])
  238. >>> nx.number_strongly_connected_components(G)
  239. 3
  240. See Also
  241. --------
  242. strongly_connected_components
  243. number_connected_components
  244. number_weakly_connected_components
  245. Notes
  246. -----
  247. For directed graphs only.
  248. """
  249. return sum(1 for scc in strongly_connected_components(G))
  250. @not_implemented_for("undirected")
  251. def is_strongly_connected(G):
  252. """Test directed graph for strong connectivity.
  253. A directed graph is strongly connected if and only if every vertex in
  254. the graph is reachable from every other vertex.
  255. Parameters
  256. ----------
  257. G : NetworkX Graph
  258. A directed graph.
  259. Returns
  260. -------
  261. connected : bool
  262. True if the graph is strongly connected, False otherwise.
  263. Examples
  264. --------
  265. >>> G = nx.DiGraph([(0, 1), (1, 2), (2, 3), (3, 0), (2, 4), (4, 2)])
  266. >>> nx.is_strongly_connected(G)
  267. True
  268. >>> G.remove_edge(2, 3)
  269. >>> nx.is_strongly_connected(G)
  270. False
  271. Raises
  272. ------
  273. NetworkXNotImplemented
  274. If G is undirected.
  275. See Also
  276. --------
  277. is_weakly_connected
  278. is_semiconnected
  279. is_connected
  280. is_biconnected
  281. strongly_connected_components
  282. Notes
  283. -----
  284. For directed graphs only.
  285. """
  286. if len(G) == 0:
  287. raise nx.NetworkXPointlessConcept(
  288. """Connectivity is undefined for the null graph."""
  289. )
  290. return len(next(strongly_connected_components(G))) == len(G)
  291. @not_implemented_for("undirected")
  292. def condensation(G, scc=None):
  293. """Returns the condensation of G.
  294. The condensation of G is the graph with each of the strongly connected
  295. components contracted into a single node.
  296. Parameters
  297. ----------
  298. G : NetworkX DiGraph
  299. A directed graph.
  300. scc: list or generator (optional, default=None)
  301. Strongly connected components. If provided, the elements in
  302. `scc` must partition the nodes in `G`. If not provided, it will be
  303. calculated as scc=nx.strongly_connected_components(G).
  304. Returns
  305. -------
  306. C : NetworkX DiGraph
  307. The condensation graph C of G. The node labels are integers
  308. corresponding to the index of the component in the list of
  309. strongly connected components of G. C has a graph attribute named
  310. 'mapping' with a dictionary mapping the original nodes to the
  311. nodes in C to which they belong. Each node in C also has a node
  312. attribute 'members' with the set of original nodes in G that
  313. form the SCC that the node in C represents.
  314. Raises
  315. ------
  316. NetworkXNotImplemented
  317. If G is undirected.
  318. Examples
  319. --------
  320. Contracting two sets of strongly connected nodes into two distinct SCC
  321. using the barbell graph.
  322. >>> G = nx.barbell_graph(4, 0)
  323. >>> G.remove_edge(3, 4)
  324. >>> G = nx.DiGraph(G)
  325. >>> H = nx.condensation(G)
  326. >>> H.nodes.data()
  327. NodeDataView({0: {'members': {0, 1, 2, 3}}, 1: {'members': {4, 5, 6, 7}}})
  328. >>> H.graph['mapping']
  329. {0: 0, 1: 0, 2: 0, 3: 0, 4: 1, 5: 1, 6: 1, 7: 1}
  330. Contracting a complete graph into one single SCC.
  331. >>> G = nx.complete_graph(7, create_using=nx.DiGraph)
  332. >>> H = nx.condensation(G)
  333. >>> H.nodes
  334. NodeView((0,))
  335. >>> H.nodes.data()
  336. NodeDataView({0: {'members': {0, 1, 2, 3, 4, 5, 6}}})
  337. Notes
  338. -----
  339. After contracting all strongly connected components to a single node,
  340. the resulting graph is a directed acyclic graph.
  341. """
  342. if scc is None:
  343. scc = nx.strongly_connected_components(G)
  344. mapping = {}
  345. members = {}
  346. C = nx.DiGraph()
  347. # Add mapping dict as graph attribute
  348. C.graph["mapping"] = mapping
  349. if len(G) == 0:
  350. return C
  351. for i, component in enumerate(scc):
  352. members[i] = component
  353. mapping.update((n, i) for n in component)
  354. number_of_components = i + 1
  355. C.add_nodes_from(range(number_of_components))
  356. C.add_edges_from(
  357. (mapping[u], mapping[v]) for u, v in G.edges() if mapping[u] != mapping[v]
  358. )
  359. # Add a list of members (ie original nodes) to each node (ie scc) in C.
  360. nx.set_node_attributes(C, members, "members")
  361. return C