123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975 |
- from heapq import heappop, heappush
- from itertools import count
- import networkx as nx
- from networkx.algorithms.shortest_paths.weighted import _weight_function
- from networkx.utils import not_implemented_for, pairwise
- __all__ = [
- "all_simple_paths",
- "is_simple_path",
- "shortest_simple_paths",
- "all_simple_edge_paths",
- ]
- @nx._dispatch
- def is_simple_path(G, nodes):
- """Returns True if and only if `nodes` form a simple path in `G`.
- A *simple path* in a graph is a nonempty sequence of nodes in which
- no node appears more than once in the sequence, and each adjacent
- pair of nodes in the sequence is adjacent in the graph.
- Parameters
- ----------
- G : graph
- A NetworkX graph.
- nodes : list
- A list of one or more nodes in the graph `G`.
- Returns
- -------
- bool
- Whether the given list of nodes represents a simple path in `G`.
- Notes
- -----
- An empty list of nodes is not a path but a list of one node is a
- path. Here's an explanation why.
- This function operates on *node paths*. One could also consider
- *edge paths*. There is a bijection between node paths and edge
- paths.
- The *length of a path* is the number of edges in the path, so a list
- of nodes of length *n* corresponds to a path of length *n* - 1.
- Thus the smallest edge path would be a list of zero edges, the empty
- path. This corresponds to a list of one node.
- To convert between a node path and an edge path, you can use code
- like the following::
- >>> from networkx.utils import pairwise
- >>> nodes = [0, 1, 2, 3]
- >>> edges = list(pairwise(nodes))
- >>> edges
- [(0, 1), (1, 2), (2, 3)]
- >>> nodes = [edges[0][0]] + [v for u, v in edges]
- >>> nodes
- [0, 1, 2, 3]
- Examples
- --------
- >>> G = nx.cycle_graph(4)
- >>> nx.is_simple_path(G, [2, 3, 0])
- True
- >>> nx.is_simple_path(G, [0, 2])
- False
- """
- # The empty list is not a valid path. Could also return
- # NetworkXPointlessConcept here.
- if len(nodes) == 0:
- return False
- # If the list is a single node, just check that the node is actually
- # in the graph.
- if len(nodes) == 1:
- return nodes[0] in G
- # check that all nodes in the list are in the graph, if at least one
- # is not in the graph, then this is not a simple path
- if not all(n in G for n in nodes):
- return False
- # If the list contains repeated nodes, then it's not a simple path
- if len(set(nodes)) != len(nodes):
- return False
- # Test that each adjacent pair of nodes is adjacent.
- return all(v in G[u] for u, v in pairwise(nodes))
- def all_simple_paths(G, source, target, cutoff=None):
- """Generate all simple paths in the graph G from source to target.
- A simple path is a path with no repeated nodes.
- Parameters
- ----------
- G : NetworkX graph
- source : node
- Starting node for path
- target : nodes
- Single node or iterable of nodes at which to end path
- cutoff : integer, optional
- Depth to stop the search. Only paths of length <= cutoff are returned.
- Returns
- -------
- path_generator: generator
- A generator that produces lists of simple paths. If there are no paths
- between the source and target within the given cutoff the generator
- produces no output. If it is possible to traverse the same sequence of
- nodes in multiple ways, namely through parallel edges, then it will be
- returned multiple times (once for each viable edge combination).
- Examples
- --------
- This iterator generates lists of nodes::
- >>> G = nx.complete_graph(4)
- >>> for path in nx.all_simple_paths(G, source=0, target=3):
- ... print(path)
- ...
- [0, 1, 2, 3]
- [0, 1, 3]
- [0, 2, 1, 3]
- [0, 2, 3]
- [0, 3]
- You can generate only those paths that are shorter than a certain
- length by using the `cutoff` keyword argument::
- >>> paths = nx.all_simple_paths(G, source=0, target=3, cutoff=2)
- >>> print(list(paths))
- [[0, 1, 3], [0, 2, 3], [0, 3]]
- To get each path as the corresponding list of edges, you can use the
- :func:`networkx.utils.pairwise` helper function::
- >>> paths = nx.all_simple_paths(G, source=0, target=3)
- >>> for path in map(nx.utils.pairwise, paths):
- ... print(list(path))
- [(0, 1), (1, 2), (2, 3)]
- [(0, 1), (1, 3)]
- [(0, 2), (2, 1), (1, 3)]
- [(0, 2), (2, 3)]
- [(0, 3)]
- Pass an iterable of nodes as target to generate all paths ending in any of several nodes::
- >>> G = nx.complete_graph(4)
- >>> for path in nx.all_simple_paths(G, source=0, target=[3, 2]):
- ... print(path)
- ...
- [0, 1, 2]
- [0, 1, 2, 3]
- [0, 1, 3]
- [0, 1, 3, 2]
- [0, 2]
- [0, 2, 1, 3]
- [0, 2, 3]
- [0, 3]
- [0, 3, 1, 2]
- [0, 3, 2]
- Iterate over each path from the root nodes to the leaf nodes in a
- directed acyclic graph using a functional programming approach::
- >>> from itertools import chain
- >>> from itertools import product
- >>> from itertools import starmap
- >>> from functools import partial
- >>>
- >>> chaini = chain.from_iterable
- >>>
- >>> G = nx.DiGraph([(0, 1), (1, 2), (0, 3), (3, 2)])
- >>> roots = (v for v, d in G.in_degree() if d == 0)
- >>> leaves = (v for v, d in G.out_degree() if d == 0)
- >>> all_paths = partial(nx.all_simple_paths, G)
- >>> list(chaini(starmap(all_paths, product(roots, leaves))))
- [[0, 1, 2], [0, 3, 2]]
- The same list computed using an iterative approach::
- >>> G = nx.DiGraph([(0, 1), (1, 2), (0, 3), (3, 2)])
- >>> roots = (v for v, d in G.in_degree() if d == 0)
- >>> leaves = (v for v, d in G.out_degree() if d == 0)
- >>> all_paths = []
- >>> for root in roots:
- ... for leaf in leaves:
- ... paths = nx.all_simple_paths(G, root, leaf)
- ... all_paths.extend(paths)
- >>> all_paths
- [[0, 1, 2], [0, 3, 2]]
- Iterate over each path from the root nodes to the leaf nodes in a
- directed acyclic graph passing all leaves together to avoid unnecessary
- compute::
- >>> G = nx.DiGraph([(0, 1), (2, 1), (1, 3), (1, 4)])
- >>> roots = (v for v, d in G.in_degree() if d == 0)
- >>> leaves = [v for v, d in G.out_degree() if d == 0]
- >>> all_paths = []
- >>> for root in roots:
- ... paths = nx.all_simple_paths(G, root, leaves)
- ... all_paths.extend(paths)
- >>> all_paths
- [[0, 1, 3], [0, 1, 4], [2, 1, 3], [2, 1, 4]]
- If parallel edges offer multiple ways to traverse a given sequence of
- nodes, this sequence of nodes will be returned multiple times:
- >>> G = nx.MultiDiGraph([(0, 1), (0, 1), (1, 2)])
- >>> list(nx.all_simple_paths(G, 0, 2))
- [[0, 1, 2], [0, 1, 2]]
- Notes
- -----
- This algorithm uses a modified depth-first search to generate the
- paths [1]_. A single path can be found in $O(V+E)$ time but the
- number of simple paths in a graph can be very large, e.g. $O(n!)$ in
- the complete graph of order $n$.
- This function does not check that a path exists between `source` and
- `target`. For large graphs, this may result in very long runtimes.
- Consider using `has_path` to check that a path exists between `source` and
- `target` before calling this function on large graphs.
- References
- ----------
- .. [1] R. Sedgewick, "Algorithms in C, Part 5: Graph Algorithms",
- Addison Wesley Professional, 3rd ed., 2001.
- See Also
- --------
- all_shortest_paths, shortest_path, has_path
- """
- if source not in G:
- raise nx.NodeNotFound(f"source node {source} not in graph")
- if target in G:
- targets = {target}
- else:
- try:
- targets = set(target)
- except TypeError as err:
- raise nx.NodeNotFound(f"target node {target} not in graph") from err
- if source in targets:
- return _empty_generator()
- if cutoff is None:
- cutoff = len(G) - 1
- if cutoff < 1:
- return _empty_generator()
- if G.is_multigraph():
- return _all_simple_paths_multigraph(G, source, targets, cutoff)
- else:
- return _all_simple_paths_graph(G, source, targets, cutoff)
- def _empty_generator():
- yield from ()
- def _all_simple_paths_graph(G, source, targets, cutoff):
- visited = {source: True}
- stack = [iter(G[source])]
- while stack:
- children = stack[-1]
- child = next(children, None)
- if child is None:
- stack.pop()
- visited.popitem()
- elif len(visited) < cutoff:
- if child in visited:
- continue
- if child in targets:
- yield list(visited) + [child]
- visited[child] = True
- if targets - set(visited.keys()): # expand stack until find all targets
- stack.append(iter(G[child]))
- else:
- visited.popitem() # maybe other ways to child
- else: # len(visited) == cutoff:
- for target in (targets & (set(children) | {child})) - set(visited.keys()):
- yield list(visited) + [target]
- stack.pop()
- visited.popitem()
- def _all_simple_paths_multigraph(G, source, targets, cutoff):
- visited = {source: True}
- stack = [(v for u, v in G.edges(source))]
- while stack:
- children = stack[-1]
- child = next(children, None)
- if child is None:
- stack.pop()
- visited.popitem()
- elif len(visited) < cutoff:
- if child in visited:
- continue
- if child in targets:
- yield list(visited) + [child]
- visited[child] = True
- if targets - set(visited.keys()):
- stack.append((v for u, v in G.edges(child)))
- else:
- visited.popitem()
- else: # len(visited) == cutoff:
- for target in targets - set(visited.keys()):
- count = ([child] + list(children)).count(target)
- for i in range(count):
- yield list(visited) + [target]
- stack.pop()
- visited.popitem()
- def all_simple_edge_paths(G, source, target, cutoff=None):
- """Generate lists of edges for all simple paths in G from source to target.
- A simple path is a path with no repeated nodes.
- Parameters
- ----------
- G : NetworkX graph
- source : node
- Starting node for path
- target : nodes
- Single node or iterable of nodes at which to end path
- cutoff : integer, optional
- Depth to stop the search. Only paths of length <= cutoff are returned.
- Returns
- -------
- path_generator: generator
- A generator that produces lists of simple paths. If there are no paths
- between the source and target within the given cutoff the generator
- produces no output.
- For multigraphs, the list of edges have elements of the form `(u,v,k)`.
- Where `k` corresponds to the edge key.
- Examples
- --------
- Print the simple path edges of a Graph::
- >>> g = nx.Graph([(1, 2), (2, 4), (1, 3), (3, 4)])
- >>> for path in sorted(nx.all_simple_edge_paths(g, 1, 4)):
- ... print(path)
- [(1, 2), (2, 4)]
- [(1, 3), (3, 4)]
- Print the simple path edges of a MultiGraph. Returned edges come with
- their associated keys::
- >>> mg = nx.MultiGraph()
- >>> mg.add_edge(1, 2, key="k0")
- 'k0'
- >>> mg.add_edge(1, 2, key="k1")
- 'k1'
- >>> mg.add_edge(2, 3, key="k0")
- 'k0'
- >>> for path in sorted(nx.all_simple_edge_paths(mg, 1, 3)):
- ... print(path)
- [(1, 2, 'k0'), (2, 3, 'k0')]
- [(1, 2, 'k1'), (2, 3, 'k0')]
- Notes
- -----
- This algorithm uses a modified depth-first search to generate the
- paths [1]_. A single path can be found in $O(V+E)$ time but the
- number of simple paths in a graph can be very large, e.g. $O(n!)$ in
- the complete graph of order $n$.
- References
- ----------
- .. [1] R. Sedgewick, "Algorithms in C, Part 5: Graph Algorithms",
- Addison Wesley Professional, 3rd ed., 2001.
- See Also
- --------
- all_shortest_paths, shortest_path, all_simple_paths
- """
- if source not in G:
- raise nx.NodeNotFound("source node %s not in graph" % source)
- if target in G:
- targets = {target}
- else:
- try:
- targets = set(target)
- except TypeError:
- raise nx.NodeNotFound("target node %s not in graph" % target)
- if source in targets:
- return []
- if cutoff is None:
- cutoff = len(G) - 1
- if cutoff < 1:
- return []
- if G.is_multigraph():
- for simp_path in _all_simple_edge_paths_multigraph(G, source, targets, cutoff):
- yield simp_path
- else:
- for simp_path in _all_simple_paths_graph(G, source, targets, cutoff):
- yield list(zip(simp_path[:-1], simp_path[1:]))
- def _all_simple_edge_paths_multigraph(G, source, targets, cutoff):
- if not cutoff or cutoff < 1:
- return []
- visited = [source]
- stack = [iter(G.edges(source, keys=True))]
- while stack:
- children = stack[-1]
- child = next(children, None)
- if child is None:
- stack.pop()
- visited.pop()
- elif len(visited) < cutoff:
- if child[1] in targets:
- yield visited[1:] + [child]
- elif child[1] not in [v[0] for v in visited[1:]]:
- visited.append(child)
- stack.append(iter(G.edges(child[1], keys=True)))
- else: # len(visited) == cutoff:
- for u, v, k in [child] + list(children):
- if v in targets:
- yield visited[1:] + [(u, v, k)]
- stack.pop()
- visited.pop()
- @not_implemented_for("multigraph")
- def shortest_simple_paths(G, source, target, weight=None):
- """Generate all simple paths in the graph G from source to target,
- starting from shortest ones.
- A simple path is a path with no repeated nodes.
- If a weighted shortest path search is to be used, no negative weights
- are allowed.
- Parameters
- ----------
- G : NetworkX graph
- source : node
- Starting node for path
- target : node
- Ending node for path
- weight : string or function
- If it is a string, it is the name of the edge attribute to be
- used as a weight.
- If it is a function, the weight of an edge is the value returned
- by the function. The function must accept exactly three positional
- arguments: the two endpoints of an edge and the dictionary of edge
- attributes for that edge. The function must return a number.
- If None all edges are considered to have unit weight. Default
- value None.
- Returns
- -------
- path_generator: generator
- A generator that produces lists of simple paths, in order from
- shortest to longest.
- Raises
- ------
- NetworkXNoPath
- If no path exists between source and target.
- NetworkXError
- If source or target nodes are not in the input graph.
- NetworkXNotImplemented
- If the input graph is a Multi[Di]Graph.
- Examples
- --------
- >>> G = nx.cycle_graph(7)
- >>> paths = list(nx.shortest_simple_paths(G, 0, 3))
- >>> print(paths)
- [[0, 1, 2, 3], [0, 6, 5, 4, 3]]
- You can use this function to efficiently compute the k shortest/best
- paths between two nodes.
- >>> from itertools import islice
- >>> def k_shortest_paths(G, source, target, k, weight=None):
- ... return list(
- ... islice(nx.shortest_simple_paths(G, source, target, weight=weight), k)
- ... )
- >>> for path in k_shortest_paths(G, 0, 3, 2):
- ... print(path)
- [0, 1, 2, 3]
- [0, 6, 5, 4, 3]
- Notes
- -----
- This procedure is based on algorithm by Jin Y. Yen [1]_. Finding
- the first $K$ paths requires $O(KN^3)$ operations.
- See Also
- --------
- all_shortest_paths
- shortest_path
- all_simple_paths
- References
- ----------
- .. [1] Jin Y. Yen, "Finding the K Shortest Loopless Paths in a
- Network", Management Science, Vol. 17, No. 11, Theory Series
- (Jul., 1971), pp. 712-716.
- """
- if source not in G:
- raise nx.NodeNotFound(f"source node {source} not in graph")
- if target not in G:
- raise nx.NodeNotFound(f"target node {target} not in graph")
- if weight is None:
- length_func = len
- shortest_path_func = _bidirectional_shortest_path
- else:
- wt = _weight_function(G, weight)
- def length_func(path):
- return sum(
- wt(u, v, G.get_edge_data(u, v)) for (u, v) in zip(path, path[1:])
- )
- shortest_path_func = _bidirectional_dijkstra
- listA = []
- listB = PathBuffer()
- prev_path = None
- while True:
- if not prev_path:
- length, path = shortest_path_func(G, source, target, weight=weight)
- listB.push(length, path)
- else:
- ignore_nodes = set()
- ignore_edges = set()
- for i in range(1, len(prev_path)):
- root = prev_path[:i]
- root_length = length_func(root)
- for path in listA:
- if path[:i] == root:
- ignore_edges.add((path[i - 1], path[i]))
- try:
- length, spur = shortest_path_func(
- G,
- root[-1],
- target,
- ignore_nodes=ignore_nodes,
- ignore_edges=ignore_edges,
- weight=weight,
- )
- path = root[:-1] + spur
- listB.push(root_length + length, path)
- except nx.NetworkXNoPath:
- pass
- ignore_nodes.add(root[-1])
- if listB:
- path = listB.pop()
- yield path
- listA.append(path)
- prev_path = path
- else:
- break
- class PathBuffer:
- def __init__(self):
- self.paths = set()
- self.sortedpaths = []
- self.counter = count()
- def __len__(self):
- return len(self.sortedpaths)
- def push(self, cost, path):
- hashable_path = tuple(path)
- if hashable_path not in self.paths:
- heappush(self.sortedpaths, (cost, next(self.counter), path))
- self.paths.add(hashable_path)
- def pop(self):
- (cost, num, path) = heappop(self.sortedpaths)
- hashable_path = tuple(path)
- self.paths.remove(hashable_path)
- return path
- def _bidirectional_shortest_path(
- G, source, target, ignore_nodes=None, ignore_edges=None, weight=None
- ):
- """Returns the shortest path between source and target ignoring
- nodes and edges in the containers ignore_nodes and ignore_edges.
- This is a custom modification of the standard bidirectional shortest
- path implementation at networkx.algorithms.unweighted
- Parameters
- ----------
- G : NetworkX graph
- source : node
- starting node for path
- target : node
- ending node for path
- ignore_nodes : container of nodes
- nodes to ignore, optional
- ignore_edges : container of edges
- edges to ignore, optional
- weight : None
- This function accepts a weight argument for convenience of
- shortest_simple_paths function. It will be ignored.
- Returns
- -------
- path: list
- List of nodes in a path from source to target.
- Raises
- ------
- NetworkXNoPath
- If no path exists between source and target.
- See Also
- --------
- shortest_path
- """
- # call helper to do the real work
- results = _bidirectional_pred_succ(G, source, target, ignore_nodes, ignore_edges)
- pred, succ, w = results
- # build path from pred+w+succ
- path = []
- # from w to target
- while w is not None:
- path.append(w)
- w = succ[w]
- # from source to w
- w = pred[path[0]]
- while w is not None:
- path.insert(0, w)
- w = pred[w]
- return len(path), path
- def _bidirectional_pred_succ(G, source, target, ignore_nodes=None, ignore_edges=None):
- """Bidirectional shortest path helper.
- Returns (pred,succ,w) where
- pred is a dictionary of predecessors from w to the source, and
- succ is a dictionary of successors from w to the target.
- """
- # does BFS from both source and target and meets in the middle
- if ignore_nodes and (source in ignore_nodes or target in ignore_nodes):
- raise nx.NetworkXNoPath(f"No path between {source} and {target}.")
- if target == source:
- return ({target: None}, {source: None}, source)
- # handle either directed or undirected
- if G.is_directed():
- Gpred = G.predecessors
- Gsucc = G.successors
- else:
- Gpred = G.neighbors
- Gsucc = G.neighbors
- # support optional nodes filter
- if ignore_nodes:
- def filter_iter(nodes):
- def iterate(v):
- for w in nodes(v):
- if w not in ignore_nodes:
- yield w
- return iterate
- Gpred = filter_iter(Gpred)
- Gsucc = filter_iter(Gsucc)
- # support optional edges filter
- if ignore_edges:
- if G.is_directed():
- def filter_pred_iter(pred_iter):
- def iterate(v):
- for w in pred_iter(v):
- if (w, v) not in ignore_edges:
- yield w
- return iterate
- def filter_succ_iter(succ_iter):
- def iterate(v):
- for w in succ_iter(v):
- if (v, w) not in ignore_edges:
- yield w
- return iterate
- Gpred = filter_pred_iter(Gpred)
- Gsucc = filter_succ_iter(Gsucc)
- else:
- def filter_iter(nodes):
- def iterate(v):
- for w in nodes(v):
- if (v, w) not in ignore_edges and (w, v) not in ignore_edges:
- yield w
- return iterate
- Gpred = filter_iter(Gpred)
- Gsucc = filter_iter(Gsucc)
- # predecesssor and successors in search
- pred = {source: None}
- succ = {target: None}
- # initialize fringes, start with forward
- forward_fringe = [source]
- reverse_fringe = [target]
- while forward_fringe and reverse_fringe:
- if len(forward_fringe) <= len(reverse_fringe):
- this_level = forward_fringe
- forward_fringe = []
- for v in this_level:
- for w in Gsucc(v):
- if w not in pred:
- forward_fringe.append(w)
- pred[w] = v
- if w in succ:
- # found path
- return pred, succ, w
- else:
- this_level = reverse_fringe
- reverse_fringe = []
- for v in this_level:
- for w in Gpred(v):
- if w not in succ:
- succ[w] = v
- reverse_fringe.append(w)
- if w in pred:
- # found path
- return pred, succ, w
- raise nx.NetworkXNoPath(f"No path between {source} and {target}.")
- def _bidirectional_dijkstra(
- G, source, target, weight="weight", ignore_nodes=None, ignore_edges=None
- ):
- """Dijkstra's algorithm for shortest paths using bidirectional search.
- This function returns the shortest path between source and target
- ignoring nodes and edges in the containers ignore_nodes and
- ignore_edges.
- This is a custom modification of the standard Dijkstra bidirectional
- shortest path implementation at networkx.algorithms.weighted
- Parameters
- ----------
- G : NetworkX graph
- source : node
- Starting node.
- target : node
- Ending node.
- weight: string, function, optional (default='weight')
- Edge data key or weight function corresponding to the edge weight
- ignore_nodes : container of nodes
- nodes to ignore, optional
- ignore_edges : container of edges
- edges to ignore, optional
- Returns
- -------
- length : number
- Shortest path length.
- Returns a tuple of two dictionaries keyed by node.
- The first dictionary stores distance from the source.
- The second stores the path from the source to that node.
- Raises
- ------
- NetworkXNoPath
- If no path exists between source and target.
- Notes
- -----
- Edge weight attributes must be numerical.
- Distances are calculated as sums of weighted edges traversed.
- In practice bidirectional Dijkstra is much more than twice as fast as
- ordinary Dijkstra.
- Ordinary Dijkstra expands nodes in a sphere-like manner from the
- source. The radius of this sphere will eventually be the length
- of the shortest path. Bidirectional Dijkstra will expand nodes
- from both the source and the target, making two spheres of half
- this radius. Volume of the first sphere is pi*r*r while the
- others are 2*pi*r/2*r/2, making up half the volume.
- This algorithm is not guaranteed to work if edge weights
- are negative or are floating point numbers
- (overflows and roundoff errors can cause problems).
- See Also
- --------
- shortest_path
- shortest_path_length
- """
- if ignore_nodes and (source in ignore_nodes or target in ignore_nodes):
- raise nx.NetworkXNoPath(f"No path between {source} and {target}.")
- if source == target:
- if source not in G:
- raise nx.NodeNotFound(f"Node {source} not in graph")
- return (0, [source])
- # handle either directed or undirected
- if G.is_directed():
- Gpred = G.predecessors
- Gsucc = G.successors
- else:
- Gpred = G.neighbors
- Gsucc = G.neighbors
- # support optional nodes filter
- if ignore_nodes:
- def filter_iter(nodes):
- def iterate(v):
- for w in nodes(v):
- if w not in ignore_nodes:
- yield w
- return iterate
- Gpred = filter_iter(Gpred)
- Gsucc = filter_iter(Gsucc)
- # support optional edges filter
- if ignore_edges:
- if G.is_directed():
- def filter_pred_iter(pred_iter):
- def iterate(v):
- for w in pred_iter(v):
- if (w, v) not in ignore_edges:
- yield w
- return iterate
- def filter_succ_iter(succ_iter):
- def iterate(v):
- for w in succ_iter(v):
- if (v, w) not in ignore_edges:
- yield w
- return iterate
- Gpred = filter_pred_iter(Gpred)
- Gsucc = filter_succ_iter(Gsucc)
- else:
- def filter_iter(nodes):
- def iterate(v):
- for w in nodes(v):
- if (v, w) not in ignore_edges and (w, v) not in ignore_edges:
- yield w
- return iterate
- Gpred = filter_iter(Gpred)
- Gsucc = filter_iter(Gsucc)
- push = heappush
- pop = heappop
- # Init: Forward Backward
- dists = [{}, {}] # dictionary of final distances
- paths = [{source: [source]}, {target: [target]}] # dictionary of paths
- fringe = [[], []] # heap of (distance, node) tuples for
- # extracting next node to expand
- seen = [{source: 0}, {target: 0}] # dictionary of distances to
- # nodes seen
- c = count()
- # initialize fringe heap
- push(fringe[0], (0, next(c), source))
- push(fringe[1], (0, next(c), target))
- # neighs for extracting correct neighbor information
- neighs = [Gsucc, Gpred]
- # variables to hold shortest discovered path
- # finaldist = 1e30000
- finalpath = []
- dir = 1
- while fringe[0] and fringe[1]:
- # choose direction
- # dir == 0 is forward direction and dir == 1 is back
- dir = 1 - dir
- # extract closest to expand
- (dist, _, v) = pop(fringe[dir])
- if v in dists[dir]:
- # Shortest path to v has already been found
- continue
- # update distance
- dists[dir][v] = dist # equal to seen[dir][v]
- if v in dists[1 - dir]:
- # if we have scanned v in both directions we are done
- # we have now discovered the shortest path
- return (finaldist, finalpath)
- wt = _weight_function(G, weight)
- for w in neighs[dir](v):
- if dir == 0: # forward
- minweight = wt(v, w, G.get_edge_data(v, w))
- vwLength = dists[dir][v] + minweight
- else: # back, must remember to change v,w->w,v
- minweight = wt(w, v, G.get_edge_data(w, v))
- vwLength = dists[dir][v] + minweight
- if w in dists[dir]:
- if vwLength < dists[dir][w]:
- raise ValueError("Contradictory paths found: negative weights?")
- elif w not in seen[dir] or vwLength < seen[dir][w]:
- # relaxing
- seen[dir][w] = vwLength
- push(fringe[dir], (vwLength, next(c), w))
- paths[dir][w] = paths[dir][v] + [w]
- if w in seen[0] and w in seen[1]:
- # see if this path is better than the already
- # discovered shortest path
- totaldist = seen[0][w] + seen[1][w]
- if finalpath == [] or finaldist > totaldist:
- finaldist = totaldist
- revpath = paths[1][w][:]
- revpath.reverse()
- finalpath = paths[0][w] + revpath[1:]
- raise nx.NetworkXNoPath(f"No path between {source} and {target}.")
|