Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/networkx/algorithms/traversal/breadth_first_search.py: 20%
121 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-20 07:00 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-20 07:00 +0000
1"""Basic algorithms for breadth-first searching the nodes of a graph."""
2import math
3from collections import deque
5import networkx as nx
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]
19@nx._dispatch
20def generic_bfs_edges(G, source, neighbors=None, depth_limit=None, sort_neighbors=None):
21 """Iterate over edges in a breadth-first search.
23 The breadth-first search begins at `source` and enqueues the
24 neighbors of newly visited nodes specified by the `neighbors`
25 function.
27 Parameters
28 ----------
29 G : NetworkX graph
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.
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.
44 depth_limit : int, optional(default=len(G))
45 Specify the maximum search depth.
47 sort_neighbors : Callable
49 .. deprecated:: 3.2
51 The sort_neighbors parameter is deprecated and will be removed in
52 version 3.4. A custom (e.g. sorted) ordering of neighbors can be
53 specified with the `neighbors` parameter.
55 A function that takes the list of neighbors of a given node as input,
56 and returns an iterator over these neighbors but with a custom
57 ordering.
59 Yields
60 ------
61 edge
62 Edges in the breadth-first search starting from `source`.
64 Examples
65 --------
66 >>> G = nx.path_graph(3)
67 >>> list(nx.bfs_edges(G, 0))
68 [(0, 1), (1, 2)]
69 >>> list(nx.bfs_edges(G, source=0, depth_limit=1))
70 [(0, 1)]
72 Notes
73 -----
74 This implementation is from `PADS`_, which was in the public domain
75 when it was first accessed in July, 2004. The modifications
76 to allow depth limits are based on the Wikipedia article
77 "`Depth-limited-search`_".
79 .. _PADS: http://www.ics.uci.edu/~eppstein/PADS/BFS.py
80 .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
81 """
82 if neighbors is None:
83 neighbors = G.neighbors
84 if sort_neighbors is not None:
85 import warnings
87 warnings.warn(
88 (
89 "The sort_neighbors parameter is deprecated and will be removed\n"
90 "in NetworkX 3.4, use the neighbors parameter instead."
91 ),
92 DeprecationWarning,
93 stacklevel=2,
94 )
95 _neighbors = neighbors
96 neighbors = lambda node: iter(sort_neighbors(_neighbors(node)))
97 if depth_limit is None:
98 depth_limit = len(G)
100 seen = {source}
101 n = len(G)
102 depth = 0
103 next_parents_children = [(source, neighbors(source))]
104 while next_parents_children and depth < depth_limit:
105 this_parents_children = next_parents_children
106 next_parents_children = []
107 for parent, children in this_parents_children:
108 for child in children:
109 if child not in seen:
110 seen.add(child)
111 next_parents_children.append((child, neighbors(child)))
112 yield parent, child
113 if len(seen) == n:
114 return
115 depth += 1
118@nx._dispatch
119def bfs_edges(G, source, reverse=False, depth_limit=None, sort_neighbors=None):
120 """Iterate over edges in a breadth-first-search starting at source.
122 Parameters
123 ----------
124 G : NetworkX graph
126 source : node
127 Specify starting node for breadth-first search; this function
128 iterates over only those edges in the component reachable from
129 this node.
131 reverse : bool, optional
132 If True traverse a directed graph in the reverse direction
134 depth_limit : int, optional(default=len(G))
135 Specify the maximum search depth
137 sort_neighbors : function
138 A function that takes the list of neighbors of given node as input, and
139 returns an *iterator* over these neighbors but with custom ordering.
141 Yields
142 ------
143 edge: 2-tuple of nodes
144 Yields edges resulting from the breadth-first search.
146 Examples
147 --------
148 To get the edges in a breadth-first search::
150 >>> G = nx.path_graph(3)
151 >>> list(nx.bfs_edges(G, 0))
152 [(0, 1), (1, 2)]
153 >>> list(nx.bfs_edges(G, source=0, depth_limit=1))
154 [(0, 1)]
156 To get the nodes in a breadth-first search order::
158 >>> G = nx.path_graph(3)
159 >>> root = 2
160 >>> edges = nx.bfs_edges(G, root)
161 >>> nodes = [root] + [v for u, v in edges]
162 >>> nodes
163 [2, 1, 0]
165 Notes
166 -----
167 The naming of this function is very similar to
168 :func:`~networkx.algorithms.traversal.edgebfs.edge_bfs`. The difference
169 is that ``edge_bfs`` yields edges even if they extend back to an already
170 explored node while this generator yields the edges of the tree that results
171 from a breadth-first-search (BFS) so no edges are reported if they extend
172 to already explored nodes. That means ``edge_bfs`` reports all edges while
173 ``bfs_edges`` only reports those traversed by a node-based BFS. Yet another
174 description is that ``bfs_edges`` reports the edges traversed during BFS
175 while ``edge_bfs`` reports all edges in the order they are explored.
177 Based on the breadth-first search implementation in PADS [1]_
178 by D. Eppstein, July 2004; with modifications to allow depth limits
179 as described in [2]_.
181 References
182 ----------
183 .. [1] http://www.ics.uci.edu/~eppstein/PADS/BFS.py.
184 .. [2] https://en.wikipedia.org/wiki/Depth-limited_search
186 See Also
187 --------
188 bfs_tree
189 :func:`~networkx.algorithms.traversal.depth_first_search.dfs_edges`
190 :func:`~networkx.algorithms.traversal.edgebfs.edge_bfs`
192 """
193 if reverse and G.is_directed():
194 successors = G.predecessors
195 else:
196 successors = G.neighbors
198 if callable(sort_neighbors):
199 yield from generic_bfs_edges(
200 G, source, lambda node: iter(sort_neighbors(successors(node))), depth_limit
201 )
202 else:
203 yield from generic_bfs_edges(G, source, successors, depth_limit)
206@nx._dispatch
207def bfs_tree(G, source, reverse=False, depth_limit=None, sort_neighbors=None):
208 """Returns an oriented tree constructed from of a breadth-first-search
209 starting at source.
211 Parameters
212 ----------
213 G : NetworkX graph
215 source : node
216 Specify starting node for breadth-first search
218 reverse : bool, optional
219 If True traverse a directed graph in the reverse direction
221 depth_limit : int, optional(default=len(G))
222 Specify the maximum search depth
224 sort_neighbors : function
225 A function that takes the list of neighbors of given node as input, and
226 returns an *iterator* over these neighbors but with custom ordering.
228 Returns
229 -------
230 T: NetworkX DiGraph
231 An oriented tree
233 Examples
234 --------
235 >>> G = nx.path_graph(3)
236 >>> list(nx.bfs_tree(G, 1).edges())
237 [(1, 0), (1, 2)]
238 >>> H = nx.Graph()
239 >>> nx.add_path(H, [0, 1, 2, 3, 4, 5, 6])
240 >>> nx.add_path(H, [2, 7, 8, 9, 10])
241 >>> sorted(list(nx.bfs_tree(H, source=3, depth_limit=3).edges()))
242 [(1, 0), (2, 1), (2, 7), (3, 2), (3, 4), (4, 5), (5, 6), (7, 8)]
245 Notes
246 -----
247 Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py
248 by D. Eppstein, July 2004. The modifications
249 to allow depth limits based on the Wikipedia article
250 "`Depth-limited-search`_".
252 .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
254 See Also
255 --------
256 dfs_tree
257 bfs_edges
258 edge_bfs
259 """
260 T = nx.DiGraph()
261 T.add_node(source)
262 edges_gen = bfs_edges(
263 G,
264 source,
265 reverse=reverse,
266 depth_limit=depth_limit,
267 sort_neighbors=sort_neighbors,
268 )
269 T.add_edges_from(edges_gen)
270 return T
273@nx._dispatch
274def bfs_predecessors(G, source, depth_limit=None, sort_neighbors=None):
275 """Returns an iterator of predecessors in breadth-first-search from source.
277 Parameters
278 ----------
279 G : NetworkX graph
281 source : node
282 Specify starting node for breadth-first search
284 depth_limit : int, optional(default=len(G))
285 Specify the maximum search depth
287 sort_neighbors : function
288 A function that takes the list of neighbors of given node as input, and
289 returns an *iterator* over these neighbors but with custom ordering.
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`.
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)]
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`_".
324 .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
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)
338@nx._dispatch
339def bfs_successors(G, source, depth_limit=None, sort_neighbors=None):
340 """Returns an iterator of successors in breadth-first-search from source.
342 Parameters
343 ----------
344 G : NetworkX graph
346 source : node
347 Specify starting node for breadth-first search
349 depth_limit : int, optional(default=len(G))
350 Specify the maximum search depth
352 sort_neighbors : function
353 A function that takes the list of neighbors of given node as input, and
354 returns an *iterator* over these neighbors but with custom ordering.
356 Returns
357 -------
358 succ: iterator
359 (node, successors) iterator where `successors` is the non-empty list of
360 successors of `node` in a breadth first search from `source`.
361 To appear in the iterator, `node` must have successors.
363 Examples
364 --------
365 >>> G = nx.path_graph(3)
366 >>> dict(nx.bfs_successors(G, 0))
367 {0: [1], 1: [2]}
368 >>> H = nx.Graph()
369 >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
370 >>> dict(nx.bfs_successors(H, 0))
371 {0: [1, 2], 1: [3, 4], 2: [5, 6]}
372 >>> G = nx.Graph()
373 >>> nx.add_path(G, [0, 1, 2, 3, 4, 5, 6])
374 >>> nx.add_path(G, [2, 7, 8, 9, 10])
375 >>> dict(nx.bfs_successors(G, source=1, depth_limit=3))
376 {1: [0, 2], 2: [3, 7], 3: [4], 7: [8]}
377 >>> G = nx.DiGraph()
378 >>> nx.add_path(G, [0, 1, 2, 3, 4, 5])
379 >>> dict(nx.bfs_successors(G, source=3))
380 {3: [4], 4: [5]}
382 Notes
383 -----
384 Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py
385 by D. Eppstein, July 2004.The modifications
386 to allow depth limits based on the Wikipedia article
387 "`Depth-limited-search`_".
389 .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
391 See Also
392 --------
393 bfs_tree
394 bfs_edges
395 edge_bfs
396 """
397 parent = source
398 children = []
399 for p, c in bfs_edges(
400 G, source, depth_limit=depth_limit, sort_neighbors=sort_neighbors
401 ):
402 if p == parent:
403 children.append(c)
404 continue
405 yield (parent, children)
406 children = [c]
407 parent = p
408 yield (parent, children)
411@nx._dispatch
412def bfs_layers(G, sources):
413 """Returns an iterator of all the layers in breadth-first search traversal.
415 Parameters
416 ----------
417 G : NetworkX graph
418 A graph over which to find the layers using breadth-first search.
420 sources : node in `G` or list of nodes in `G`
421 Specify starting nodes for single source or multiple sources breadth-first search
423 Yields
424 ------
425 layer: list of nodes
426 Yields list of nodes at the same distance from sources
428 Examples
429 --------
430 >>> G = nx.path_graph(5)
431 >>> dict(enumerate(nx.bfs_layers(G, [0, 4])))
432 {0: [0, 4], 1: [1, 3], 2: [2]}
433 >>> H = nx.Graph()
434 >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
435 >>> dict(enumerate(nx.bfs_layers(H, [1])))
436 {0: [1], 1: [0, 3, 4], 2: [2], 3: [5, 6]}
437 >>> dict(enumerate(nx.bfs_layers(H, [1, 6])))
438 {0: [1, 6], 1: [0, 3, 4, 2], 2: [5]}
439 """
440 if sources in G:
441 sources = [sources]
443 current_layer = list(sources)
444 visited = set(sources)
446 for source in current_layer:
447 if source not in G:
448 raise nx.NetworkXError(f"The node {source} is not in the graph.")
450 # this is basically BFS, except that the current layer only stores the nodes at
451 # same distance from sources at each iteration
452 while current_layer:
453 yield current_layer
454 next_layer = []
455 for node in current_layer:
456 for child in G[node]:
457 if child not in visited:
458 visited.add(child)
459 next_layer.append(child)
460 current_layer = next_layer
463REVERSE_EDGE = "reverse"
464TREE_EDGE = "tree"
465FORWARD_EDGE = "forward"
466LEVEL_EDGE = "level"
469@nx._dispatch
470def bfs_labeled_edges(G, sources):
471 """Iterate over edges in a breadth-first search (BFS) labeled by type.
473 We generate triple of the form (*u*, *v*, *d*), where (*u*, *v*) is the
474 edge being explored in the breadth-first search and *d* is one of the
475 strings 'tree', 'forward', 'level', or 'reverse'. A 'tree' edge is one in
476 which *v* is first discovered and placed into the layer below *u*. A
477 'forward' edge is one in which *u* is on the layer above *v* and *v* has
478 already been discovered. A 'level' edge is one in which both *u* and *v*
479 occur on the same layer. A 'reverse' edge is one in which *u* is on a layer
480 below *v*.
482 We emit each edge exactly once. In an undirected graph, 'reverse' edges do
483 not occur, because each is discovered either as a 'tree' or 'forward' edge.
485 Parameters
486 ----------
487 G : NetworkX graph
488 A graph over which to find the layers using breadth-first search.
490 sources : node in `G` or list of nodes in `G`
491 Starting nodes for single source or multiple sources breadth-first search
493 Yields
494 ------
495 edges: generator
496 A generator of triples (*u*, *v*, *d*) where (*u*, *v*) is the edge being
497 explored and *d* is described above.
499 Examples
500 --------
501 >>> G = nx.cycle_graph(4, create_using = nx.DiGraph)
502 >>> list(nx.bfs_labeled_edges(G, 0))
503 [(0, 1, 'tree'), (1, 2, 'tree'), (2, 3, 'tree'), (3, 0, 'reverse')]
504 >>> G = nx.complete_graph(3)
505 >>> list(nx.bfs_labeled_edges(G, 0))
506 [(0, 1, 'tree'), (0, 2, 'tree'), (1, 2, 'level')]
507 >>> list(nx.bfs_labeled_edges(G, [0, 1]))
508 [(0, 1, 'level'), (0, 2, 'tree'), (1, 2, 'forward')]
509 """
510 if sources in G:
511 sources = [sources]
513 neighbors = G._adj
514 directed = G.is_directed()
515 visited = set()
516 visit = visited.discard if directed else visited.add
517 # We use visited in a negative sense, so the visited set stays empty for the
518 # directed case and level edges are reported on their first occurrence in
519 # the undirected case. Note our use of visited.discard -- this is built-in
520 # thus somewhat faster than a python-defined def nop(x): pass
521 depth = {s: 0 for s in sources}
522 queue = deque(depth.items())
523 push = queue.append
524 pop = queue.popleft
525 while queue:
526 u, du = pop()
527 for v in neighbors[u]:
528 if v not in depth:
529 depth[v] = dv = du + 1
530 push((v, dv))
531 yield u, v, TREE_EDGE
532 else:
533 dv = depth[v]
534 if du == dv:
535 if v not in visited:
536 yield u, v, LEVEL_EDGE
537 elif du < dv:
538 yield u, v, FORWARD_EDGE
539 elif directed:
540 yield u, v, REVERSE_EDGE
541 visit(u)
544@nx._dispatch
545def descendants_at_distance(G, source, distance):
546 """Returns all nodes at a fixed `distance` from `source` in `G`.
548 Parameters
549 ----------
550 G : NetworkX graph
551 A graph
552 source : node in `G`
553 distance : the distance of the wanted nodes from `source`
555 Returns
556 -------
557 set()
558 The descendants of `source` in `G` at the given `distance` from `source`
560 Examples
561 --------
562 >>> G = nx.path_graph(5)
563 >>> nx.descendants_at_distance(G, 2, 2)
564 {0, 4}
565 >>> H = nx.DiGraph()
566 >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
567 >>> nx.descendants_at_distance(H, 0, 2)
568 {3, 4, 5, 6}
569 >>> nx.descendants_at_distance(H, 5, 0)
570 {5}
571 >>> nx.descendants_at_distance(H, 5, 1)
572 set()
573 """
574 if source not in G:
575 raise nx.NetworkXError(f"The node {source} is not in the graph.")
577 bfs_generator = nx.bfs_layers(G, source)
578 for i, layer in enumerate(bfs_generator):
579 if i == distance:
580 return set(layer)
581 return set()