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 Parameters
270 ----------
271 G : NetworkX graph
272
273 source : node
274 Specify starting node for breadth-first search
275
276 depth_limit : int, optional(default=len(G))
277 Specify the maximum search depth
278
279 sort_neighbors : function (default=None)
280 A function that takes an iterator over nodes as the input, and
281 returns an iterable of the same nodes with a custom ordering.
282 For example, `sorted` will sort the nodes in increasing order.
283
284 Returns
285 -------
286 pred: iterator
287 (node, predecessor) iterator where `predecessor` is the predecessor of
288 `node` in a breadth first search starting from `source`.
289
290 Examples
291 --------
292 >>> G = nx.path_graph(3)
293 >>> dict(nx.bfs_predecessors(G, 0))
294 {1: 0, 2: 1}
295 >>> H = nx.Graph()
296 >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
297 >>> dict(nx.bfs_predecessors(H, 0))
298 {1: 0, 2: 0, 3: 1, 4: 1, 5: 2, 6: 2}
299 >>> M = nx.Graph()
300 >>> nx.add_path(M, [0, 1, 2, 3, 4, 5, 6])
301 >>> nx.add_path(M, [2, 7, 8, 9, 10])
302 >>> sorted(nx.bfs_predecessors(M, source=1, depth_limit=3))
303 [(0, 1), (2, 1), (3, 2), (4, 3), (7, 2), (8, 7)]
304 >>> N = nx.DiGraph()
305 >>> nx.add_path(N, [0, 1, 2, 3, 4, 7])
306 >>> nx.add_path(N, [3, 5, 6, 7])
307 >>> sorted(nx.bfs_predecessors(N, source=2))
308 [(3, 2), (4, 3), (5, 3), (6, 5), (7, 4)]
309
310 Notes
311 -----
312 Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py
313 by D. Eppstein, July 2004. The modifications
314 to allow depth limits based on the Wikipedia article
315 "`Depth-limited-search`_".
316
317 .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
318
319 See Also
320 --------
321 bfs_tree
322 bfs_edges
323 edge_bfs
324 """
325 for s, t in bfs_edges(
326 G, source, depth_limit=depth_limit, sort_neighbors=sort_neighbors
327 ):
328 yield (t, s)
329
330
331@nx._dispatchable
332def bfs_successors(G, source, depth_limit=None, sort_neighbors=None):
333 """Returns an iterator of successors in breadth-first-search from source.
334
335 Parameters
336 ----------
337 G : NetworkX graph
338
339 source : node
340 Specify starting node for breadth-first search
341
342 depth_limit : int, optional(default=len(G))
343 Specify the maximum search depth
344
345 sort_neighbors : function (default=None)
346 A function that takes an iterator over nodes as the input, and
347 returns an iterable of the same nodes with a custom ordering.
348 For example, `sorted` will sort the nodes in increasing order.
349
350 Returns
351 -------
352 succ: iterator
353 (node, successors) iterator where `successors` is the non-empty list of
354 successors of `node` in a breadth first search from `source`.
355 To appear in the iterator, `node` must have successors.
356
357 Examples
358 --------
359 >>> G = nx.path_graph(3)
360 >>> dict(nx.bfs_successors(G, 0))
361 {0: [1], 1: [2]}
362 >>> H = nx.Graph()
363 >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
364 >>> dict(nx.bfs_successors(H, 0))
365 {0: [1, 2], 1: [3, 4], 2: [5, 6]}
366 >>> G = nx.Graph()
367 >>> nx.add_path(G, [0, 1, 2, 3, 4, 5, 6])
368 >>> nx.add_path(G, [2, 7, 8, 9, 10])
369 >>> dict(nx.bfs_successors(G, source=1, depth_limit=3))
370 {1: [0, 2], 2: [3, 7], 3: [4], 7: [8]}
371 >>> G = nx.DiGraph()
372 >>> nx.add_path(G, [0, 1, 2, 3, 4, 5])
373 >>> dict(nx.bfs_successors(G, source=3))
374 {3: [4], 4: [5]}
375
376 Notes
377 -----
378 Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py
379 by D. Eppstein, July 2004.The modifications
380 to allow depth limits based on the Wikipedia article
381 "`Depth-limited-search`_".
382
383 .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
384
385 See Also
386 --------
387 bfs_tree
388 bfs_edges
389 edge_bfs
390 """
391 parent = source
392 children = []
393 for p, c in bfs_edges(
394 G, source, depth_limit=depth_limit, sort_neighbors=sort_neighbors
395 ):
396 if p == parent:
397 children.append(c)
398 continue
399 yield (parent, children)
400 children = [c]
401 parent = p
402 yield (parent, children)
403
404
405@nx._dispatchable
406def bfs_layers(G, sources):
407 """Returns an iterator of all the layers in breadth-first search traversal.
408
409 Parameters
410 ----------
411 G : NetworkX graph
412 A graph over which to find the layers using breadth-first search.
413
414 sources : node in `G` or iterable of nodes in `G`
415 Specify starting nodes for single source or multiple sources
416 breadth-first search.
417
418 Yields
419 ------
420 layer : list of nodes
421 Yields list of nodes at the same distance from `sources`.
422
423 Examples
424 --------
425 >>> G = nx.path_graph(5)
426 >>> dict(enumerate(nx.bfs_layers(G, [0, 4])))
427 {0: [0, 4], 1: [1, 3], 2: [2]}
428 >>> H = nx.Graph()
429 >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
430 >>> dict(enumerate(nx.bfs_layers(H, [1])))
431 {0: [1], 1: [0, 3, 4], 2: [2], 3: [5, 6]}
432 >>> dict(enumerate(nx.bfs_layers(H, [1, 6])))
433 {0: [1, 6], 1: [0, 3, 4, 2], 2: [5]}
434 """
435 if sources in G:
436 sources = [sources]
437
438 visited = set(sources)
439 current_layer = list(visited)
440
441 for source in current_layer:
442 if source not in G:
443 raise nx.NetworkXError(f"The node {source} is not in the graph.")
444
445 # this is basically BFS, except that the current layer only stores the nodes at
446 # same distance from sources at each iteration
447 while current_layer:
448 yield current_layer
449 next_layer = []
450 for node in current_layer:
451 for child in G[node]:
452 if child not in visited:
453 visited.add(child)
454 next_layer.append(child)
455 current_layer = next_layer
456
457
458REVERSE_EDGE = "reverse"
459TREE_EDGE = "tree"
460FORWARD_EDGE = "forward"
461LEVEL_EDGE = "level"
462
463
464@nx._dispatchable
465def bfs_labeled_edges(G, sources):
466 """Iterate over edges in a breadth-first search (BFS) labeled by type.
467
468 We generate triple of the form (*u*, *v*, *d*), where (*u*, *v*) is the
469 edge being explored in the breadth-first search and *d* is one of the
470 strings 'tree', 'forward', 'level', or 'reverse'. A 'tree' edge is one in
471 which *v* is first discovered and placed into the layer below *u*. A
472 'forward' edge is one in which *u* is on the layer above *v* and *v* has
473 already been discovered. A 'level' edge is one in which both *u* and *v*
474 occur on the same layer. A 'reverse' edge is one in which *u* is on a layer
475 below *v*.
476
477 We emit each edge exactly once. In an undirected graph, 'reverse' edges do
478 not occur, because each is discovered either as a 'tree' or 'forward' edge.
479
480 Parameters
481 ----------
482 G : NetworkX graph
483 A graph over which to find the layers using breadth-first search.
484
485 sources : node in `G` or list of nodes in `G`
486 Starting nodes for single source or multiple sources breadth-first search
487
488 Yields
489 ------
490 edges: generator
491 A generator of triples (*u*, *v*, *d*) where (*u*, *v*) is the edge being
492 explored and *d* is described above.
493
494 Examples
495 --------
496 >>> G = nx.cycle_graph(4, create_using=nx.DiGraph)
497 >>> list(nx.bfs_labeled_edges(G, 0))
498 [(0, 1, 'tree'), (1, 2, 'tree'), (2, 3, 'tree'), (3, 0, 'reverse')]
499 >>> G = nx.complete_graph(3)
500 >>> list(nx.bfs_labeled_edges(G, 0))
501 [(0, 1, 'tree'), (0, 2, 'tree'), (1, 2, 'level')]
502 >>> list(nx.bfs_labeled_edges(G, [0, 1]))
503 [(0, 1, 'level'), (0, 2, 'tree'), (1, 2, 'forward')]
504 """
505 if sources in G:
506 sources = [sources]
507
508 neighbors = G._adj
509 directed = G.is_directed()
510 visited = set()
511 visit = visited.discard if directed else visited.add
512 # We use visited in a negative sense, so the visited set stays empty for the
513 # directed case and level edges are reported on their first occurrence in
514 # the undirected case. Note our use of visited.discard -- this is built-in
515 # thus somewhat faster than a python-defined def nop(x): pass
516 depth = {s: 0 for s in sources}
517 queue = deque(depth.items())
518 push = queue.append
519 pop = queue.popleft
520 while queue:
521 u, du = pop()
522 for v in neighbors[u]:
523 if v not in depth:
524 depth[v] = dv = du + 1
525 push((v, dv))
526 yield u, v, TREE_EDGE
527 else:
528 dv = depth[v]
529 if du == dv:
530 if v not in visited:
531 yield u, v, LEVEL_EDGE
532 elif du < dv:
533 yield u, v, FORWARD_EDGE
534 elif directed:
535 yield u, v, REVERSE_EDGE
536 visit(u)
537
538
539@nx._dispatchable
540def descendants_at_distance(G, source, distance):
541 """Returns all nodes at a fixed `distance` from `source` in `G`.
542
543 Parameters
544 ----------
545 G : NetworkX graph
546 A graph
547 source : node in `G`
548 distance : the distance of the wanted nodes from `source`
549
550 Returns
551 -------
552 set()
553 The descendants of `source` in `G` at the given `distance` from `source`
554
555 Examples
556 --------
557 >>> G = nx.path_graph(5)
558 >>> nx.descendants_at_distance(G, 2, 2)
559 {0, 4}
560 >>> H = nx.DiGraph()
561 >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
562 >>> nx.descendants_at_distance(H, 0, 2)
563 {3, 4, 5, 6}
564 >>> nx.descendants_at_distance(H, 5, 0)
565 {5}
566 >>> nx.descendants_at_distance(H, 5, 1)
567 set()
568 """
569 if source not in G:
570 raise nx.NetworkXError(f"The node {source} is not in the graph.")
571
572 bfs_generator = nx.bfs_layers(G, source)
573 for i, layer in enumerate(bfs_generator):
574 if i == distance:
575 return set(layer)
576 return set()