1"""Basic algorithms for breadth-first searching the nodes of a graph."""
2
3from collections import deque
4
5import networkx as nx
6
7__all__ = [
8 "bfs_edges",
9 "bfs_tree",
10 "bfs_predecessors",
11 "bfs_successors",
12 "descendants_at_distance",
13 "bfs_layers",
14 "bfs_labeled_edges",
15 "generic_bfs_edges",
16]
17
18
19@nx._dispatchable
20def generic_bfs_edges(G, source, neighbors=None, depth_limit=None):
21 """Iterate over edges in a breadth-first search.
22
23 The breadth-first search begins at `source` and enqueues the
24 neighbors of newly visited nodes specified by the `neighbors`
25 function.
26
27 Parameters
28 ----------
29 G : NetworkX graph
30
31 source : node
32 Starting node for the breadth-first search; this function
33 iterates over only those edges in the component reachable from
34 this node.
35
36 neighbors : function
37 A function that takes a newly visited node of the graph as input
38 and returns an *iterator* (not just a list) of nodes that are
39 neighbors of that node with custom ordering. If not specified, this is
40 just the ``G.neighbors`` method, but in general it can be any function
41 that returns an iterator over some or all of the neighbors of a
42 given node, in any order.
43
44 depth_limit : int, optional(default=len(G))
45 Specify the maximum search depth.
46
47 Yields
48 ------
49 edge
50 Edges in the breadth-first search starting from `source`.
51
52 Examples
53 --------
54 >>> G = nx.path_graph(7)
55 >>> list(nx.generic_bfs_edges(G, source=0))
56 [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)]
57 >>> list(nx.generic_bfs_edges(G, source=2))
58 [(2, 1), (2, 3), (1, 0), (3, 4), (4, 5), (5, 6)]
59 >>> list(nx.generic_bfs_edges(G, source=2, depth_limit=2))
60 [(2, 1), (2, 3), (1, 0), (3, 4)]
61
62 The `neighbors` param can be used to specify the visitation order of each
63 node's neighbors generically. In the following example, we modify the default
64 neighbor to return *odd* nodes first:
65
66 >>> def odd_first(n):
67 ... return sorted(G.neighbors(n), key=lambda x: x % 2, reverse=True)
68
69 >>> G = nx.star_graph(5)
70 >>> list(nx.generic_bfs_edges(G, source=0)) # Default neighbor ordering
71 [(0, 1), (0, 2), (0, 3), (0, 4), (0, 5)]
72 >>> list(nx.generic_bfs_edges(G, source=0, neighbors=odd_first))
73 [(0, 1), (0, 3), (0, 5), (0, 2), (0, 4)]
74
75 Notes
76 -----
77 This implementation is from `PADS`_, which was in the public domain
78 when it was first accessed in July, 2004. The modifications
79 to allow depth limits are based on the Wikipedia article
80 "`Depth-limited-search`_".
81
82 .. _PADS: http://www.ics.uci.edu/~eppstein/PADS/BFS.py
83 .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
84 """
85 if neighbors is None:
86 neighbors = G.neighbors
87 if depth_limit is None:
88 depth_limit = len(G)
89
90 seen = {source}
91 n = len(G)
92 depth = 0
93 next_parents_children = [(source, neighbors(source))]
94 while next_parents_children and depth < depth_limit:
95 this_parents_children = next_parents_children
96 next_parents_children = []
97 for parent, children in this_parents_children:
98 for child in children:
99 if child not in seen:
100 seen.add(child)
101 next_parents_children.append((child, neighbors(child)))
102 yield parent, child
103 if len(seen) == n:
104 return
105 depth += 1
106
107
108@nx._dispatchable
109def bfs_edges(G, source, reverse=False, depth_limit=None, sort_neighbors=None):
110 """Iterate over edges in a breadth-first-search starting at source.
111
112 Parameters
113 ----------
114 G : NetworkX graph
115
116 source : node
117 Specify starting node for breadth-first search; this function
118 iterates over only those edges in the component reachable from
119 this node.
120
121 reverse : bool, optional
122 If True traverse a directed graph in the reverse direction
123
124 depth_limit : int, optional(default=len(G))
125 Specify the maximum search depth
126
127 sort_neighbors : function (default=None)
128 A function that takes an iterator over nodes as the input, and
129 returns an iterable of the same nodes with a custom ordering.
130 For example, `sorted` will sort the nodes in increasing order.
131
132 Yields
133 ------
134 edge: 2-tuple of nodes
135 Yields edges resulting from the breadth-first search.
136
137 Examples
138 --------
139 To get the edges in a breadth-first search:
140
141 >>> G = nx.path_graph(3)
142 >>> list(nx.bfs_edges(G, 0))
143 [(0, 1), (1, 2)]
144 >>> list(nx.bfs_edges(G, source=0, depth_limit=1))
145 [(0, 1)]
146
147 To get the nodes in a breadth-first search order:
148
149 >>> G = nx.path_graph(3)
150 >>> root = 2
151 >>> edges = nx.bfs_edges(G, root)
152 >>> nodes = [root] + [v for u, v in edges]
153 >>> nodes
154 [2, 1, 0]
155
156 Notes
157 -----
158 The naming of this function is very similar to
159 :func:`~networkx.algorithms.traversal.edgebfs.edge_bfs`. The difference
160 is that ``edge_bfs`` yields edges even if they extend back to an already
161 explored node while this generator yields the edges of the tree that results
162 from a breadth-first-search (BFS) so no edges are reported if they extend
163 to already explored nodes. That means ``edge_bfs`` reports all edges while
164 ``bfs_edges`` only reports those traversed by a node-based BFS. Yet another
165 description is that ``bfs_edges`` reports the edges traversed during BFS
166 while ``edge_bfs`` reports all edges in the order they are explored.
167
168 Based on the breadth-first search implementation in PADS [1]_
169 by D. Eppstein, July 2004; with modifications to allow depth limits
170 as described in [2]_.
171
172 References
173 ----------
174 .. [1] http://www.ics.uci.edu/~eppstein/PADS/BFS.py.
175 .. [2] https://en.wikipedia.org/wiki/Depth-limited_search
176
177 See Also
178 --------
179 bfs_tree
180 :func:`~networkx.algorithms.traversal.depth_first_search.dfs_edges`
181 :func:`~networkx.algorithms.traversal.edgebfs.edge_bfs`
182
183 """
184 if reverse and G.is_directed():
185 successors = G.predecessors
186 else:
187 successors = G.neighbors
188
189 if sort_neighbors is not None:
190 yield from generic_bfs_edges(
191 G, source, lambda node: iter(sort_neighbors(successors(node))), depth_limit
192 )
193 else:
194 yield from generic_bfs_edges(G, source, successors, depth_limit)
195
196
197@nx._dispatchable(returns_graph=True)
198def bfs_tree(G, source, reverse=False, depth_limit=None, sort_neighbors=None):
199 """Returns an oriented tree constructed from of a breadth-first-search
200 starting at source.
201
202 Parameters
203 ----------
204 G : NetworkX graph
205
206 source : node
207 Specify starting node for breadth-first search
208
209 reverse : bool, optional
210 If True traverse a directed graph in the reverse direction
211
212 depth_limit : int, optional(default=len(G))
213 Specify the maximum search depth
214
215 sort_neighbors : function (default=None)
216 A function that takes an iterator over nodes as the input, and
217 returns an iterable of the same nodes with a custom ordering.
218 For example, `sorted` will sort the nodes in increasing order.
219
220 Returns
221 -------
222 T: NetworkX DiGraph
223 An oriented tree
224
225 Examples
226 --------
227 >>> G = nx.path_graph(3)
228 >>> list(nx.bfs_tree(G, 1).edges())
229 [(1, 0), (1, 2)]
230 >>> H = nx.Graph()
231 >>> nx.add_path(H, [0, 1, 2, 3, 4, 5, 6])
232 >>> nx.add_path(H, [2, 7, 8, 9, 10])
233 >>> sorted(list(nx.bfs_tree(H, source=3, depth_limit=3).edges()))
234 [(1, 0), (2, 1), (2, 7), (3, 2), (3, 4), (4, 5), (5, 6), (7, 8)]
235
236
237 Notes
238 -----
239 Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py
240 by D. Eppstein, July 2004. The modifications
241 to allow depth limits based on the Wikipedia article
242 "`Depth-limited-search`_".
243
244 .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
245
246 See Also
247 --------
248 dfs_tree
249 bfs_edges
250 edge_bfs
251 """
252 T = nx.DiGraph()
253 T.add_node(source)
254 edges_gen = bfs_edges(
255 G,
256 source,
257 reverse=reverse,
258 depth_limit=depth_limit,
259 sort_neighbors=sort_neighbors,
260 )
261 T.add_edges_from(edges_gen)
262 return T
263
264
265@nx._dispatchable
266def bfs_predecessors(G, source, depth_limit=None, sort_neighbors=None):
267 """Returns an iterator of predecessors in breadth-first-search from source.
268
269 Each yielded ``(node, predecessor)`` tuple describes a node and
270 the node from which it is discovered in the breadth-first-search.
271
272 The term "predecessor" here is not the directed graph term "predecessor".
273 We are not doing BFS in reverse direction. We are just reporting the
274 preceding node for each node in a BFS.
275
276 Parameters
277 ----------
278 G : NetworkX graph
279
280 source : node
281 Specify starting node for breadth-first search
282
283 depth_limit : int, optional(default=len(G))
284 Specify the maximum search depth
285
286 sort_neighbors : function (default=None)
287 A function that takes an iterator over nodes as the input, and
288 returns an iterable of the same nodes with a custom ordering.
289 For example, `sorted` will sort the nodes in increasing order.
290
291 Returns
292 -------
293 pred: iterator
294 (node, predecessor) iterator where `predecessor` is the predecessor of
295 `node` in a breadth first search starting from `source`.
296
297 Examples
298 --------
299 >>> G = nx.path_graph(3)
300 >>> dict(nx.bfs_predecessors(G, 0))
301 {1: 0, 2: 1}
302 >>> H = nx.Graph()
303 >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
304 >>> dict(nx.bfs_predecessors(H, 0))
305 {1: 0, 2: 0, 3: 1, 4: 1, 5: 2, 6: 2}
306 >>> M = nx.Graph()
307 >>> nx.add_path(M, [0, 1, 2, 3, 4, 5, 6])
308 >>> nx.add_path(M, [2, 7, 8, 9, 10])
309 >>> sorted(nx.bfs_predecessors(M, source=1, depth_limit=3))
310 [(0, 1), (2, 1), (3, 2), (4, 3), (7, 2), (8, 7)]
311 >>> N = nx.DiGraph()
312 >>> nx.add_path(N, [0, 1, 2, 3, 4, 7])
313 >>> nx.add_path(N, [3, 5, 6, 7])
314 >>> sorted(nx.bfs_predecessors(N, source=2))
315 [(3, 2), (4, 3), (5, 3), (6, 5), (7, 4)]
316
317 Notes
318 -----
319 Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py
320 by D. Eppstein, July 2004. The modifications
321 to allow depth limits based on the Wikipedia article
322 "`Depth-limited-search`_".
323
324 .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
325
326 See Also
327 --------
328 bfs_tree
329 bfs_edges
330 edge_bfs
331 """
332 for s, t in bfs_edges(
333 G, source, depth_limit=depth_limit, sort_neighbors=sort_neighbors
334 ):
335 yield (t, s)
336
337
338@nx._dispatchable
339def bfs_successors(G, source, depth_limit=None, sort_neighbors=None):
340 """Returns an iterator of successors in breadth-first-search from source.
341
342 Parameters
343 ----------
344 G : NetworkX graph
345
346 source : node
347 Specify starting node for breadth-first search
348
349 depth_limit : int, optional(default=len(G))
350 Specify the maximum search depth
351
352 sort_neighbors : function (default=None)
353 A function that takes an iterator over nodes as the input, and
354 returns an iterable of the same nodes with a custom ordering.
355 For example, `sorted` will sort the nodes in increasing order.
356
357 Returns
358 -------
359 succ: iterator
360 (node, successors) iterator where `successors` is the non-empty list of
361 successors of `node` in a breadth first search from `source`.
362 To appear in the iterator, `node` must have successors.
363
364 Examples
365 --------
366 >>> G = nx.path_graph(3)
367 >>> dict(nx.bfs_successors(G, 0))
368 {0: [1], 1: [2]}
369 >>> H = nx.Graph()
370 >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
371 >>> dict(nx.bfs_successors(H, 0))
372 {0: [1, 2], 1: [3, 4], 2: [5, 6]}
373 >>> G = nx.Graph()
374 >>> nx.add_path(G, [0, 1, 2, 3, 4, 5, 6])
375 >>> nx.add_path(G, [2, 7, 8, 9, 10])
376 >>> dict(nx.bfs_successors(G, source=1, depth_limit=3))
377 {1: [0, 2], 2: [3, 7], 3: [4], 7: [8]}
378 >>> G = nx.DiGraph()
379 >>> nx.add_path(G, [0, 1, 2, 3, 4, 5])
380 >>> dict(nx.bfs_successors(G, source=3))
381 {3: [4], 4: [5]}
382
383 Notes
384 -----
385 Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py
386 by D. Eppstein, July 2004.The modifications
387 to allow depth limits based on the Wikipedia article
388 "`Depth-limited-search`_".
389
390 .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
391
392 See Also
393 --------
394 bfs_tree
395 bfs_edges
396 edge_bfs
397 """
398 parent = source
399 children = []
400 for p, c in bfs_edges(
401 G, source, depth_limit=depth_limit, sort_neighbors=sort_neighbors
402 ):
403 if p == parent:
404 children.append(c)
405 continue
406 yield (parent, children)
407 children = [c]
408 parent = p
409 yield (parent, children)
410
411
412@nx._dispatchable
413def bfs_layers(G, sources):
414 """Returns an iterator of all the layers in breadth-first search traversal.
415
416 Parameters
417 ----------
418 G : NetworkX graph
419 A graph over which to find the layers using breadth-first search.
420
421 sources : node in `G` or iterable of nodes in `G`
422 Specify starting nodes for single source or multiple sources
423 breadth-first search.
424
425 Yields
426 ------
427 layer : list of nodes
428 Yields list of nodes at the same distance from `sources`.
429
430 Examples
431 --------
432 >>> G = nx.path_graph(5)
433 >>> dict(enumerate(nx.bfs_layers(G, [0, 4])))
434 {0: [0, 4], 1: [1, 3], 2: [2]}
435 >>> H = nx.Graph()
436 >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
437 >>> dict(enumerate(nx.bfs_layers(H, [1])))
438 {0: [1], 1: [0, 3, 4], 2: [2], 3: [5, 6]}
439 >>> dict(enumerate(nx.bfs_layers(H, [1, 6])))
440 {0: [1, 6], 1: [0, 3, 4, 2], 2: [5]}
441 """
442 if sources in G:
443 sources = [sources]
444
445 visited = set(sources)
446 current_layer = list(visited)
447
448 for source in current_layer:
449 if source not in G:
450 raise nx.NetworkXError(f"The node {source} is not in the graph.")
451
452 # this is basically BFS, except that the current layer only stores the nodes at
453 # same distance from sources at each iteration
454 while current_layer:
455 yield current_layer
456 next_layer = []
457 for node in current_layer:
458 for child in G[node]:
459 if child not in visited:
460 visited.add(child)
461 next_layer.append(child)
462 current_layer = next_layer
463
464
465REVERSE_EDGE = "reverse"
466TREE_EDGE = "tree"
467FORWARD_EDGE = "forward"
468LEVEL_EDGE = "level"
469
470
471@nx._dispatchable
472def bfs_labeled_edges(G, sources):
473 """Iterate over edges in a breadth-first search (BFS) labeled by type.
474
475 We generate triple of the form (*u*, *v*, *d*), where (*u*, *v*) is the
476 edge being explored in the breadth-first search and *d* is one of the
477 strings 'tree', 'forward', 'level', or 'reverse'. A 'tree' edge is one in
478 which *v* is first discovered and placed into the layer below *u*. A
479 'forward' edge is one in which *u* is on the layer above *v* and *v* has
480 already been discovered. A 'level' edge is one in which both *u* and *v*
481 occur on the same layer. A 'reverse' edge is one in which *u* is on a layer
482 below *v*.
483
484 We emit each edge exactly once. In an undirected graph, 'reverse' edges do
485 not occur, because each is discovered either as a 'tree' or 'forward' edge.
486
487 Parameters
488 ----------
489 G : NetworkX graph
490 A graph over which to find the layers using breadth-first search.
491
492 sources : node in `G` or list of nodes in `G`
493 Starting nodes for single source or multiple sources breadth-first search
494
495 Yields
496 ------
497 edges: generator
498 A generator of triples (*u*, *v*, *d*) where (*u*, *v*) is the edge being
499 explored and *d* is described above.
500
501 Examples
502 --------
503 >>> G = nx.cycle_graph(4, create_using=nx.DiGraph)
504 >>> list(nx.bfs_labeled_edges(G, 0))
505 [(0, 1, 'tree'), (1, 2, 'tree'), (2, 3, 'tree'), (3, 0, 'reverse')]
506 >>> G = nx.complete_graph(3)
507 >>> list(nx.bfs_labeled_edges(G, 0))
508 [(0, 1, 'tree'), (0, 2, 'tree'), (1, 2, 'level')]
509 >>> list(nx.bfs_labeled_edges(G, [0, 1]))
510 [(0, 1, 'level'), (0, 2, 'tree'), (1, 2, 'forward')]
511 """
512 if sources in G:
513 sources = [sources]
514
515 neighbors = G._adj
516 directed = G.is_directed()
517 visited = set()
518 visit = visited.discard if directed else visited.add
519 # We use visited in a negative sense, so the visited set stays empty for the
520 # directed case and level edges are reported on their first occurrence in
521 # the undirected case. Note our use of visited.discard -- this is built-in
522 # thus somewhat faster than a python-defined def nop(x): pass
523 depth = {s: 0 for s in sources}
524 queue = deque(depth.items())
525 push = queue.append
526 pop = queue.popleft
527 while queue:
528 u, du = pop()
529 for v in neighbors[u]:
530 if v not in depth:
531 depth[v] = dv = du + 1
532 push((v, dv))
533 yield u, v, TREE_EDGE
534 else:
535 dv = depth[v]
536 if du == dv:
537 if v not in visited:
538 yield u, v, LEVEL_EDGE
539 elif du < dv:
540 yield u, v, FORWARD_EDGE
541 elif directed:
542 yield u, v, REVERSE_EDGE
543 visit(u)
544
545
546@nx._dispatchable
547def descendants_at_distance(G, source, distance):
548 """Returns all nodes at a fixed `distance` from `source` in `G`.
549
550 Parameters
551 ----------
552 G : NetworkX graph
553 A graph
554 source : node in `G`
555 distance : the distance of the wanted nodes from `source`
556
557 Returns
558 -------
559 set()
560 The descendants of `source` in `G` at the given `distance` from `source`
561
562 Examples
563 --------
564 >>> G = nx.path_graph(5)
565 >>> nx.descendants_at_distance(G, 2, 2)
566 {0, 4}
567 >>> H = nx.DiGraph()
568 >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
569 >>> nx.descendants_at_distance(H, 0, 2)
570 {3, 4, 5, 6}
571 >>> nx.descendants_at_distance(H, 5, 0)
572 {5}
573 >>> nx.descendants_at_distance(H, 5, 1)
574 set()
575 """
576 if source not in G:
577 raise nx.NetworkXError(f"The node {source} is not in the graph.")
578
579 bfs_generator = nx.bfs_layers(G, source)
580 for i, layer in enumerate(bfs_generator):
581 if i == distance:
582 return set(layer)
583 return set()