"""Basic algorithms for breadth-first searching the nodes of a graph."""
from collections import deque
import networkx as nx
__all__ = [
"bfs_edges",
"bfs_tree",
"bfs_predecessors",
"bfs_successors",
"descendants_at_distance",
"bfs_layers",
"bfs_labeled_edges",
"generic_bfs_edges",
]
[docs]
@nx._dispatchable
def generic_bfs_edges(G, source, neighbors=None, depth_limit=None):
"""Iterate over edges in a breadth-first search.
The breadth-first search begins at `source` and enqueues the
neighbors of newly visited nodes specified by the `neighbors`
function.
Parameters
----------
G : NetworkX graph
source : node
Starting node for the breadth-first search; this function
iterates over only those edges in the component reachable from
this node.
neighbors : function
A function that takes a newly visited node of the graph as input
and returns an *iterator* (not just a list) of nodes that are
neighbors of that node with custom ordering. If not specified, this is
just the ``G.neighbors`` method, but in general it can be any function
that returns an iterator over some or all of the neighbors of a
given node, in any order.
depth_limit : int, optional(default=len(G))
Specify the maximum search depth.
Yields
------
edge
Edges in the breadth-first search starting from `source`.
Examples
--------
>>> G = nx.path_graph(7)
>>> list(nx.generic_bfs_edges(G, source=0))
[(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)]
>>> list(nx.generic_bfs_edges(G, source=2))
[(2, 1), (2, 3), (1, 0), (3, 4), (4, 5), (5, 6)]
>>> list(nx.generic_bfs_edges(G, source=2, depth_limit=2))
[(2, 1), (2, 3), (1, 0), (3, 4)]
The `neighbors` param can be used to specify the visitation order of each
node's neighbors generically. In the following example, we modify the default
neighbor to return *odd* nodes first:
>>> def odd_first(n):
... return sorted(G.neighbors(n), key=lambda x: x % 2, reverse=True)
>>> G = nx.star_graph(5)
>>> list(nx.generic_bfs_edges(G, source=0)) # Default neighbor ordering
[(0, 1), (0, 2), (0, 3), (0, 4), (0, 5)]
>>> list(nx.generic_bfs_edges(G, source=0, neighbors=odd_first))
[(0, 1), (0, 3), (0, 5), (0, 2), (0, 4)]
Notes
-----
This implementation is from `PADS`_, which was in the public domain
when it was first accessed in July, 2004. The modifications
to allow depth limits are based on the Wikipedia article
"`Depth-limited-search`_".
.. _PADS: http://www.ics.uci.edu/~eppstein/PADS/BFS.py
.. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
"""
if neighbors is None:
neighbors = G.neighbors
if depth_limit is None:
depth_limit = len(G)
seen = {source}
n = len(G)
depth = 0
next_parents_children = [(source, neighbors(source))]
while next_parents_children and depth < depth_limit:
this_parents_children = next_parents_children
next_parents_children = []
for parent, children in this_parents_children:
for child in children:
if child not in seen:
seen.add(child)
next_parents_children.append((child, neighbors(child)))
yield parent, child
if len(seen) == n:
return
depth += 1
[docs]
@nx._dispatchable
def bfs_edges(G, source, reverse=False, depth_limit=None, sort_neighbors=None):
"""Iterate over edges in a breadth-first-search starting at source.
Parameters
----------
G : NetworkX graph
source : node
Specify starting node for breadth-first search; this function
iterates over only those edges in the component reachable from
this node.
reverse : bool, optional
If True traverse a directed graph in the reverse direction
depth_limit : int, optional(default=len(G))
Specify the maximum search depth
sort_neighbors : function (default=None)
A function that takes an iterator over nodes as the input, and
returns an iterable of the same nodes with a custom ordering.
For example, `sorted` will sort the nodes in increasing order.
Yields
------
edge: 2-tuple of nodes
Yields edges resulting from the breadth-first search.
Examples
--------
To get the edges in a breadth-first search::
>>> G = nx.path_graph(3)
>>> list(nx.bfs_edges(G, 0))
[(0, 1), (1, 2)]
>>> list(nx.bfs_edges(G, source=0, depth_limit=1))
[(0, 1)]
To get the nodes in a breadth-first search order::
>>> G = nx.path_graph(3)
>>> root = 2
>>> edges = nx.bfs_edges(G, root)
>>> nodes = [root] + [v for u, v in edges]
>>> nodes
[2, 1, 0]
Notes
-----
The naming of this function is very similar to
:func:`~networkx.algorithms.traversal.edgebfs.edge_bfs`. The difference
is that ``edge_bfs`` yields edges even if they extend back to an already
explored node while this generator yields the edges of the tree that results
from a breadth-first-search (BFS) so no edges are reported if they extend
to already explored nodes. That means ``edge_bfs`` reports all edges while
``bfs_edges`` only reports those traversed by a node-based BFS. Yet another
description is that ``bfs_edges`` reports the edges traversed during BFS
while ``edge_bfs`` reports all edges in the order they are explored.
Based on the breadth-first search implementation in PADS [1]_
by D. Eppstein, July 2004; with modifications to allow depth limits
as described in [2]_.
References
----------
.. [1] http://www.ics.uci.edu/~eppstein/PADS/BFS.py.
.. [2] https://en.wikipedia.org/wiki/Depth-limited_search
See Also
--------
bfs_tree
:func:`~networkx.algorithms.traversal.depth_first_search.dfs_edges`
:func:`~networkx.algorithms.traversal.edgebfs.edge_bfs`
"""
if reverse and G.is_directed():
successors = G.predecessors
else:
successors = G.neighbors
if sort_neighbors is not None:
yield from generic_bfs_edges(
G, source, lambda node: iter(sort_neighbors(successors(node))), depth_limit
)
else:
yield from generic_bfs_edges(G, source, successors, depth_limit)
[docs]
@nx._dispatchable(returns_graph=True)
def bfs_tree(G, source, reverse=False, depth_limit=None, sort_neighbors=None):
"""Returns an oriented tree constructed from of a breadth-first-search
starting at source.
Parameters
----------
G : NetworkX graph
source : node
Specify starting node for breadth-first search
reverse : bool, optional
If True traverse a directed graph in the reverse direction
depth_limit : int, optional(default=len(G))
Specify the maximum search depth
sort_neighbors : function (default=None)
A function that takes an iterator over nodes as the input, and
returns an iterable of the same nodes with a custom ordering.
For example, `sorted` will sort the nodes in increasing order.
Returns
-------
T: NetworkX DiGraph
An oriented tree
Examples
--------
>>> G = nx.path_graph(3)
>>> list(nx.bfs_tree(G, 1).edges())
[(1, 0), (1, 2)]
>>> H = nx.Graph()
>>> nx.add_path(H, [0, 1, 2, 3, 4, 5, 6])
>>> nx.add_path(H, [2, 7, 8, 9, 10])
>>> sorted(list(nx.bfs_tree(H, source=3, depth_limit=3).edges()))
[(1, 0), (2, 1), (2, 7), (3, 2), (3, 4), (4, 5), (5, 6), (7, 8)]
Notes
-----
Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py
by D. Eppstein, July 2004. The modifications
to allow depth limits based on the Wikipedia article
"`Depth-limited-search`_".
.. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
See Also
--------
dfs_tree
bfs_edges
edge_bfs
"""
T = nx.DiGraph()
T.add_node(source)
edges_gen = bfs_edges(
G,
source,
reverse=reverse,
depth_limit=depth_limit,
sort_neighbors=sort_neighbors,
)
T.add_edges_from(edges_gen)
return T
[docs]
@nx._dispatchable
def bfs_predecessors(G, source, depth_limit=None, sort_neighbors=None):
"""Returns an iterator of predecessors in breadth-first-search from source.
Parameters
----------
G : NetworkX graph
source : node
Specify starting node for breadth-first search
depth_limit : int, optional(default=len(G))
Specify the maximum search depth
sort_neighbors : function (default=None)
A function that takes an iterator over nodes as the input, and
returns an iterable of the same nodes with a custom ordering.
For example, `sorted` will sort the nodes in increasing order.
Returns
-------
pred: iterator
(node, predecessor) iterator where `predecessor` is the predecessor of
`node` in a breadth first search starting from `source`.
Examples
--------
>>> G = nx.path_graph(3)
>>> dict(nx.bfs_predecessors(G, 0))
{1: 0, 2: 1}
>>> H = nx.Graph()
>>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
>>> dict(nx.bfs_predecessors(H, 0))
{1: 0, 2: 0, 3: 1, 4: 1, 5: 2, 6: 2}
>>> M = nx.Graph()
>>> nx.add_path(M, [0, 1, 2, 3, 4, 5, 6])
>>> nx.add_path(M, [2, 7, 8, 9, 10])
>>> sorted(nx.bfs_predecessors(M, source=1, depth_limit=3))
[(0, 1), (2, 1), (3, 2), (4, 3), (7, 2), (8, 7)]
>>> N = nx.DiGraph()
>>> nx.add_path(N, [0, 1, 2, 3, 4, 7])
>>> nx.add_path(N, [3, 5, 6, 7])
>>> sorted(nx.bfs_predecessors(N, source=2))
[(3, 2), (4, 3), (5, 3), (6, 5), (7, 4)]
Notes
-----
Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py
by D. Eppstein, July 2004. The modifications
to allow depth limits based on the Wikipedia article
"`Depth-limited-search`_".
.. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
See Also
--------
bfs_tree
bfs_edges
edge_bfs
"""
for s, t in bfs_edges(
G, source, depth_limit=depth_limit, sort_neighbors=sort_neighbors
):
yield (t, s)
[docs]
@nx._dispatchable
def bfs_successors(G, source, depth_limit=None, sort_neighbors=None):
"""Returns an iterator of successors in breadth-first-search from source.
Parameters
----------
G : NetworkX graph
source : node
Specify starting node for breadth-first search
depth_limit : int, optional(default=len(G))
Specify the maximum search depth
sort_neighbors : function (default=None)
A function that takes an iterator over nodes as the input, and
returns an iterable of the same nodes with a custom ordering.
For example, `sorted` will sort the nodes in increasing order.
Returns
-------
succ: iterator
(node, successors) iterator where `successors` is the non-empty list of
successors of `node` in a breadth first search from `source`.
To appear in the iterator, `node` must have successors.
Examples
--------
>>> G = nx.path_graph(3)
>>> dict(nx.bfs_successors(G, 0))
{0: [1], 1: [2]}
>>> H = nx.Graph()
>>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
>>> dict(nx.bfs_successors(H, 0))
{0: [1, 2], 1: [3, 4], 2: [5, 6]}
>>> G = nx.Graph()
>>> nx.add_path(G, [0, 1, 2, 3, 4, 5, 6])
>>> nx.add_path(G, [2, 7, 8, 9, 10])
>>> dict(nx.bfs_successors(G, source=1, depth_limit=3))
{1: [0, 2], 2: [3, 7], 3: [4], 7: [8]}
>>> G = nx.DiGraph()
>>> nx.add_path(G, [0, 1, 2, 3, 4, 5])
>>> dict(nx.bfs_successors(G, source=3))
{3: [4], 4: [5]}
Notes
-----
Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py
by D. Eppstein, July 2004.The modifications
to allow depth limits based on the Wikipedia article
"`Depth-limited-search`_".
.. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
See Also
--------
bfs_tree
bfs_edges
edge_bfs
"""
parent = source
children = []
for p, c in bfs_edges(
G, source, depth_limit=depth_limit, sort_neighbors=sort_neighbors
):
if p == parent:
children.append(c)
continue
yield (parent, children)
children = [c]
parent = p
yield (parent, children)
[docs]
@nx._dispatchable
def bfs_layers(G, sources):
"""Returns an iterator of all the layers in breadth-first search traversal.
Parameters
----------
G : NetworkX graph
A graph over which to find the layers using breadth-first search.
sources : node in `G` or list of nodes in `G`
Specify starting nodes for single source or multiple sources breadth-first search
Yields
------
layer: list of nodes
Yields list of nodes at the same distance from sources
Examples
--------
>>> G = nx.path_graph(5)
>>> dict(enumerate(nx.bfs_layers(G, [0, 4])))
{0: [0, 4], 1: [1, 3], 2: [2]}
>>> H = nx.Graph()
>>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
>>> dict(enumerate(nx.bfs_layers(H, [1])))
{0: [1], 1: [0, 3, 4], 2: [2], 3: [5, 6]}
>>> dict(enumerate(nx.bfs_layers(H, [1, 6])))
{0: [1, 6], 1: [0, 3, 4, 2], 2: [5]}
"""
if sources in G:
sources = [sources]
current_layer = list(sources)
visited = set(sources)
for source in current_layer:
if source not in G:
raise nx.NetworkXError(f"The node {source} is not in the graph.")
# this is basically BFS, except that the current layer only stores the nodes at
# same distance from sources at each iteration
while current_layer:
yield current_layer
next_layer = []
for node in current_layer:
for child in G[node]:
if child not in visited:
visited.add(child)
next_layer.append(child)
current_layer = next_layer
REVERSE_EDGE = "reverse"
TREE_EDGE = "tree"
FORWARD_EDGE = "forward"
LEVEL_EDGE = "level"
@nx._dispatchable
def bfs_labeled_edges(G, sources):
"""Iterate over edges in a breadth-first search (BFS) labeled by type.
We generate triple of the form (*u*, *v*, *d*), where (*u*, *v*) is the
edge being explored in the breadth-first search and *d* is one of the
strings 'tree', 'forward', 'level', or 'reverse'. A 'tree' edge is one in
which *v* is first discovered and placed into the layer below *u*. A
'forward' edge is one in which *u* is on the layer above *v* and *v* has
already been discovered. A 'level' edge is one in which both *u* and *v*
occur on the same layer. A 'reverse' edge is one in which *u* is on a layer
below *v*.
We emit each edge exactly once. In an undirected graph, 'reverse' edges do
not occur, because each is discovered either as a 'tree' or 'forward' edge.
Parameters
----------
G : NetworkX graph
A graph over which to find the layers using breadth-first search.
sources : node in `G` or list of nodes in `G`
Starting nodes for single source or multiple sources breadth-first search
Yields
------
edges: generator
A generator of triples (*u*, *v*, *d*) where (*u*, *v*) is the edge being
explored and *d* is described above.
Examples
--------
>>> G = nx.cycle_graph(4, create_using=nx.DiGraph)
>>> list(nx.bfs_labeled_edges(G, 0))
[(0, 1, 'tree'), (1, 2, 'tree'), (2, 3, 'tree'), (3, 0, 'reverse')]
>>> G = nx.complete_graph(3)
>>> list(nx.bfs_labeled_edges(G, 0))
[(0, 1, 'tree'), (0, 2, 'tree'), (1, 2, 'level')]
>>> list(nx.bfs_labeled_edges(G, [0, 1]))
[(0, 1, 'level'), (0, 2, 'tree'), (1, 2, 'forward')]
"""
if sources in G:
sources = [sources]
neighbors = G._adj
directed = G.is_directed()
visited = set()
visit = visited.discard if directed else visited.add
# We use visited in a negative sense, so the visited set stays empty for the
# directed case and level edges are reported on their first occurrence in
# the undirected case. Note our use of visited.discard -- this is built-in
# thus somewhat faster than a python-defined def nop(x): pass
depth = {s: 0 for s in sources}
queue = deque(depth.items())
push = queue.append
pop = queue.popleft
while queue:
u, du = pop()
for v in neighbors[u]:
if v not in depth:
depth[v] = dv = du + 1
push((v, dv))
yield u, v, TREE_EDGE
else:
dv = depth[v]
if du == dv:
if v not in visited:
yield u, v, LEVEL_EDGE
elif du < dv:
yield u, v, FORWARD_EDGE
elif directed:
yield u, v, REVERSE_EDGE
visit(u)
[docs]
@nx._dispatchable
def descendants_at_distance(G, source, distance):
"""Returns all nodes at a fixed `distance` from `source` in `G`.
Parameters
----------
G : NetworkX graph
A graph
source : node in `G`
distance : the distance of the wanted nodes from `source`
Returns
-------
set()
The descendants of `source` in `G` at the given `distance` from `source`
Examples
--------
>>> G = nx.path_graph(5)
>>> nx.descendants_at_distance(G, 2, 2)
{0, 4}
>>> H = nx.DiGraph()
>>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
>>> nx.descendants_at_distance(H, 0, 2)
{3, 4, 5, 6}
>>> nx.descendants_at_distance(H, 5, 0)
{5}
>>> nx.descendants_at_distance(H, 5, 1)
set()
"""
if source not in G:
raise nx.NetworkXError(f"The node {source} is not in the graph.")
bfs_generator = nx.bfs_layers(G, source)
for i, layer in enumerate(bfs_generator):
if i == distance:
return set(layer)
return set()