breadth_first_search.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. """Basic algorithms for breadth-first searching the nodes of a graph."""
  2. from collections import deque
  3. import networkx as nx
  4. __all__ = [
  5. "bfs_edges",
  6. "bfs_tree",
  7. "bfs_predecessors",
  8. "bfs_successors",
  9. "descendants_at_distance",
  10. "bfs_layers",
  11. ]
  12. def generic_bfs_edges(G, source, neighbors=None, depth_limit=None, sort_neighbors=None):
  13. """Iterate over edges in a breadth-first search.
  14. The breadth-first search begins at `source` and enqueues the
  15. neighbors of newly visited nodes specified by the `neighbors`
  16. function.
  17. Parameters
  18. ----------
  19. G : NetworkX graph
  20. source : node
  21. Starting node for the breadth-first search; this function
  22. iterates over only those edges in the component reachable from
  23. this node.
  24. neighbors : function
  25. A function that takes a newly visited node of the graph as input
  26. and returns an *iterator* (not just a list) of nodes that are
  27. neighbors of that node. If not specified, this is just the
  28. ``G.neighbors`` method, but in general it can be any function
  29. that returns an iterator over some or all of the neighbors of a
  30. given node, in any order.
  31. depth_limit : int, optional(default=len(G))
  32. Specify the maximum search depth
  33. sort_neighbors : function
  34. A function that takes the list of neighbors of given node as input, and
  35. returns an *iterator* over these neighbors but with custom ordering.
  36. Yields
  37. ------
  38. edge
  39. Edges in the breadth-first search starting from `source`.
  40. Examples
  41. --------
  42. >>> G = nx.path_graph(3)
  43. >>> print(list(nx.bfs_edges(G, 0)))
  44. [(0, 1), (1, 2)]
  45. >>> print(list(nx.bfs_edges(G, source=0, depth_limit=1)))
  46. [(0, 1)]
  47. Notes
  48. -----
  49. This implementation is from `PADS`_, which was in the public domain
  50. when it was first accessed in July, 2004. The modifications
  51. to allow depth limits are based on the Wikipedia article
  52. "`Depth-limited-search`_".
  53. .. _PADS: http://www.ics.uci.edu/~eppstein/PADS/BFS.py
  54. .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
  55. """
  56. if callable(sort_neighbors):
  57. _neighbors = neighbors
  58. neighbors = lambda node: iter(sort_neighbors(_neighbors(node)))
  59. visited = {source}
  60. if depth_limit is None:
  61. depth_limit = len(G)
  62. queue = deque([(source, depth_limit, neighbors(source))])
  63. while queue:
  64. parent, depth_now, children = queue[0]
  65. try:
  66. child = next(children)
  67. if child not in visited:
  68. yield parent, child
  69. visited.add(child)
  70. if depth_now > 1:
  71. queue.append((child, depth_now - 1, neighbors(child)))
  72. except StopIteration:
  73. queue.popleft()
  74. @nx._dispatch
  75. def bfs_edges(G, source, reverse=False, depth_limit=None, sort_neighbors=None):
  76. """Iterate over edges in a breadth-first-search starting at source.
  77. Parameters
  78. ----------
  79. G : NetworkX graph
  80. source : node
  81. Specify starting node for breadth-first search; this function
  82. iterates over only those edges in the component reachable from
  83. this node.
  84. reverse : bool, optional
  85. If True traverse a directed graph in the reverse direction
  86. depth_limit : int, optional(default=len(G))
  87. Specify the maximum search depth
  88. sort_neighbors : function
  89. A function that takes the list of neighbors of given node as input, and
  90. returns an *iterator* over these neighbors but with custom ordering.
  91. Yields
  92. ------
  93. edge: 2-tuple of nodes
  94. Yields edges resulting from the breadth-first search.
  95. Examples
  96. --------
  97. To get the edges in a breadth-first search::
  98. >>> G = nx.path_graph(3)
  99. >>> list(nx.bfs_edges(G, 0))
  100. [(0, 1), (1, 2)]
  101. >>> list(nx.bfs_edges(G, source=0, depth_limit=1))
  102. [(0, 1)]
  103. To get the nodes in a breadth-first search order::
  104. >>> G = nx.path_graph(3)
  105. >>> root = 2
  106. >>> edges = nx.bfs_edges(G, root)
  107. >>> nodes = [root] + [v for u, v in edges]
  108. >>> nodes
  109. [2, 1, 0]
  110. Notes
  111. -----
  112. The naming of this function is very similar to
  113. :func:`~networkx.algorithms.traversal.edgebfs.edge_bfs`. The difference
  114. is that ``edge_bfs`` yields edges even if they extend back to an already
  115. explored node while this generator yields the edges of the tree that results
  116. from a breadth-first-search (BFS) so no edges are reported if they extend
  117. to already explored nodes. That means ``edge_bfs`` reports all edges while
  118. ``bfs_edges`` only reports those traversed by a node-based BFS. Yet another
  119. description is that ``bfs_edges`` reports the edges traversed during BFS
  120. while ``edge_bfs`` reports all edges in the order they are explored.
  121. Based on the breadth-first search implementation in PADS [1]_
  122. by D. Eppstein, July 2004; with modifications to allow depth limits
  123. as described in [2]_.
  124. References
  125. ----------
  126. .. [1] http://www.ics.uci.edu/~eppstein/PADS/BFS.py.
  127. .. [2] https://en.wikipedia.org/wiki/Depth-limited_search
  128. See Also
  129. --------
  130. bfs_tree
  131. :func:`~networkx.algorithms.traversal.depth_first_search.dfs_edges`
  132. :func:`~networkx.algorithms.traversal.edgebfs.edge_bfs`
  133. """
  134. if reverse and G.is_directed():
  135. successors = G.predecessors
  136. else:
  137. successors = G.neighbors
  138. yield from generic_bfs_edges(G, source, successors, depth_limit, sort_neighbors)
  139. def bfs_tree(G, source, reverse=False, depth_limit=None, sort_neighbors=None):
  140. """Returns an oriented tree constructed from of a breadth-first-search
  141. starting at source.
  142. Parameters
  143. ----------
  144. G : NetworkX graph
  145. source : node
  146. Specify starting node for breadth-first search
  147. reverse : bool, optional
  148. If True traverse a directed graph in the reverse direction
  149. depth_limit : int, optional(default=len(G))
  150. Specify the maximum search depth
  151. sort_neighbors : function
  152. A function that takes the list of neighbors of given node as input, and
  153. returns an *iterator* over these neighbors but with custom ordering.
  154. Returns
  155. -------
  156. T: NetworkX DiGraph
  157. An oriented tree
  158. Examples
  159. --------
  160. >>> G = nx.path_graph(3)
  161. >>> print(list(nx.bfs_tree(G, 1).edges()))
  162. [(1, 0), (1, 2)]
  163. >>> H = nx.Graph()
  164. >>> nx.add_path(H, [0, 1, 2, 3, 4, 5, 6])
  165. >>> nx.add_path(H, [2, 7, 8, 9, 10])
  166. >>> print(sorted(list(nx.bfs_tree(H, source=3, depth_limit=3).edges())))
  167. [(1, 0), (2, 1), (2, 7), (3, 2), (3, 4), (4, 5), (5, 6), (7, 8)]
  168. Notes
  169. -----
  170. Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py
  171. by D. Eppstein, July 2004. The modifications
  172. to allow depth limits based on the Wikipedia article
  173. "`Depth-limited-search`_".
  174. .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
  175. See Also
  176. --------
  177. dfs_tree
  178. bfs_edges
  179. edge_bfs
  180. """
  181. T = nx.DiGraph()
  182. T.add_node(source)
  183. edges_gen = bfs_edges(
  184. G,
  185. source,
  186. reverse=reverse,
  187. depth_limit=depth_limit,
  188. sort_neighbors=sort_neighbors,
  189. )
  190. T.add_edges_from(edges_gen)
  191. return T
  192. @nx._dispatch
  193. def bfs_predecessors(G, source, depth_limit=None, sort_neighbors=None):
  194. """Returns an iterator of predecessors in breadth-first-search from source.
  195. Parameters
  196. ----------
  197. G : NetworkX graph
  198. source : node
  199. Specify starting node for breadth-first search
  200. depth_limit : int, optional(default=len(G))
  201. Specify the maximum search depth
  202. sort_neighbors : function
  203. A function that takes the list of neighbors of given node as input, and
  204. returns an *iterator* over these neighbors but with custom ordering.
  205. Returns
  206. -------
  207. pred: iterator
  208. (node, predecessor) iterator where `predecessor` is the predecessor of
  209. `node` in a breadth first search starting from `source`.
  210. Examples
  211. --------
  212. >>> G = nx.path_graph(3)
  213. >>> print(dict(nx.bfs_predecessors(G, 0)))
  214. {1: 0, 2: 1}
  215. >>> H = nx.Graph()
  216. >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
  217. >>> print(dict(nx.bfs_predecessors(H, 0)))
  218. {1: 0, 2: 0, 3: 1, 4: 1, 5: 2, 6: 2}
  219. >>> M = nx.Graph()
  220. >>> nx.add_path(M, [0, 1, 2, 3, 4, 5, 6])
  221. >>> nx.add_path(M, [2, 7, 8, 9, 10])
  222. >>> print(sorted(nx.bfs_predecessors(M, source=1, depth_limit=3)))
  223. [(0, 1), (2, 1), (3, 2), (4, 3), (7, 2), (8, 7)]
  224. >>> N = nx.DiGraph()
  225. >>> nx.add_path(N, [0, 1, 2, 3, 4, 7])
  226. >>> nx.add_path(N, [3, 5, 6, 7])
  227. >>> print(sorted(nx.bfs_predecessors(N, source=2)))
  228. [(3, 2), (4, 3), (5, 3), (6, 5), (7, 4)]
  229. Notes
  230. -----
  231. Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py
  232. by D. Eppstein, July 2004. The modifications
  233. to allow depth limits based on the Wikipedia article
  234. "`Depth-limited-search`_".
  235. .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
  236. See Also
  237. --------
  238. bfs_tree
  239. bfs_edges
  240. edge_bfs
  241. """
  242. for s, t in bfs_edges(
  243. G, source, depth_limit=depth_limit, sort_neighbors=sort_neighbors
  244. ):
  245. yield (t, s)
  246. @nx._dispatch
  247. def bfs_successors(G, source, depth_limit=None, sort_neighbors=None):
  248. """Returns an iterator of successors in breadth-first-search from source.
  249. Parameters
  250. ----------
  251. G : NetworkX graph
  252. source : node
  253. Specify starting node for breadth-first search
  254. depth_limit : int, optional(default=len(G))
  255. Specify the maximum search depth
  256. sort_neighbors : function
  257. A function that takes the list of neighbors of given node as input, and
  258. returns an *iterator* over these neighbors but with custom ordering.
  259. Returns
  260. -------
  261. succ: iterator
  262. (node, successors) iterator where `successors` is the non-empty list of
  263. successors of `node` in a breadth first search from `source`.
  264. To appear in the iterator, `node` must have successors.
  265. Examples
  266. --------
  267. >>> G = nx.path_graph(3)
  268. >>> print(dict(nx.bfs_successors(G, 0)))
  269. {0: [1], 1: [2]}
  270. >>> H = nx.Graph()
  271. >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
  272. >>> print(dict(nx.bfs_successors(H, 0)))
  273. {0: [1, 2], 1: [3, 4], 2: [5, 6]}
  274. >>> G = nx.Graph()
  275. >>> nx.add_path(G, [0, 1, 2, 3, 4, 5, 6])
  276. >>> nx.add_path(G, [2, 7, 8, 9, 10])
  277. >>> print(dict(nx.bfs_successors(G, source=1, depth_limit=3)))
  278. {1: [0, 2], 2: [3, 7], 3: [4], 7: [8]}
  279. >>> G = nx.DiGraph()
  280. >>> nx.add_path(G, [0, 1, 2, 3, 4, 5])
  281. >>> print(dict(nx.bfs_successors(G, source=3)))
  282. {3: [4], 4: [5]}
  283. Notes
  284. -----
  285. Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py
  286. by D. Eppstein, July 2004.The modifications
  287. to allow depth limits based on the Wikipedia article
  288. "`Depth-limited-search`_".
  289. .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
  290. See Also
  291. --------
  292. bfs_tree
  293. bfs_edges
  294. edge_bfs
  295. """
  296. parent = source
  297. children = []
  298. for p, c in bfs_edges(
  299. G, source, depth_limit=depth_limit, sort_neighbors=sort_neighbors
  300. ):
  301. if p == parent:
  302. children.append(c)
  303. continue
  304. yield (parent, children)
  305. children = [c]
  306. parent = p
  307. yield (parent, children)
  308. @nx._dispatch
  309. def bfs_layers(G, sources):
  310. """Returns an iterator of all the layers in breadth-first search traversal.
  311. Parameters
  312. ----------
  313. G : NetworkX graph
  314. A graph over which to find the layers using breadth-first search.
  315. sources : node in `G` or list of nodes in `G`
  316. Specify starting nodes for single source or multiple sources breadth-first search
  317. Yields
  318. ------
  319. layer: list of nodes
  320. Yields list of nodes at the same distance from sources
  321. Examples
  322. --------
  323. >>> G = nx.path_graph(5)
  324. >>> dict(enumerate(nx.bfs_layers(G, [0, 4])))
  325. {0: [0, 4], 1: [1, 3], 2: [2]}
  326. >>> H = nx.Graph()
  327. >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
  328. >>> dict(enumerate(nx.bfs_layers(H, [1])))
  329. {0: [1], 1: [0, 3, 4], 2: [2], 3: [5, 6]}
  330. >>> dict(enumerate(nx.bfs_layers(H, [1, 6])))
  331. {0: [1, 6], 1: [0, 3, 4, 2], 2: [5]}
  332. """
  333. if sources in G:
  334. sources = [sources]
  335. current_layer = list(sources)
  336. visited = set(sources)
  337. for source in current_layer:
  338. if source not in G:
  339. raise nx.NetworkXError(f"The node {source} is not in the graph.")
  340. # this is basically BFS, except that the current layer only stores the nodes at
  341. # same distance from sources at each iteration
  342. while current_layer:
  343. yield current_layer
  344. next_layer = []
  345. for node in current_layer:
  346. for child in G[node]:
  347. if child not in visited:
  348. visited.add(child)
  349. next_layer.append(child)
  350. current_layer = next_layer
  351. @nx._dispatch
  352. def descendants_at_distance(G, source, distance):
  353. """Returns all nodes at a fixed `distance` from `source` in `G`.
  354. Parameters
  355. ----------
  356. G : NetworkX graph
  357. A graph
  358. source : node in `G`
  359. distance : the distance of the wanted nodes from `source`
  360. Returns
  361. -------
  362. set()
  363. The descendants of `source` in `G` at the given `distance` from `source`
  364. Examples
  365. --------
  366. >>> G = nx.path_graph(5)
  367. >>> nx.descendants_at_distance(G, 2, 2)
  368. {0, 4}
  369. >>> H = nx.DiGraph()
  370. >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
  371. >>> nx.descendants_at_distance(H, 0, 2)
  372. {3, 4, 5, 6}
  373. >>> nx.descendants_at_distance(H, 5, 0)
  374. {5}
  375. >>> nx.descendants_at_distance(H, 5, 1)
  376. set()
  377. """
  378. if source not in G:
  379. raise nx.NetworkXError(f"The node {source} is not in the graph.")
  380. bfs_generator = nx.bfs_layers(G, source)
  381. for i, layer in enumerate(bfs_generator):
  382. if i == distance:
  383. return set(layer)
  384. return set()