unweighted.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. """
  2. Shortest path algorithms for unweighted graphs.
  3. """
  4. import warnings
  5. import networkx as nx
  6. __all__ = [
  7. "bidirectional_shortest_path",
  8. "single_source_shortest_path",
  9. "single_source_shortest_path_length",
  10. "single_target_shortest_path",
  11. "single_target_shortest_path_length",
  12. "all_pairs_shortest_path",
  13. "all_pairs_shortest_path_length",
  14. "predecessor",
  15. ]
  16. @nx._dispatch
  17. def single_source_shortest_path_length(G, source, cutoff=None):
  18. """Compute the shortest path lengths from source to all reachable nodes.
  19. Parameters
  20. ----------
  21. G : NetworkX graph
  22. source : node
  23. Starting node for path
  24. cutoff : integer, optional
  25. Depth to stop the search. Only paths of length <= cutoff are returned.
  26. Returns
  27. -------
  28. lengths : dict
  29. Dict keyed by node to shortest path length to source.
  30. Examples
  31. --------
  32. >>> G = nx.path_graph(5)
  33. >>> length = nx.single_source_shortest_path_length(G, 0)
  34. >>> length[4]
  35. 4
  36. >>> for node in length:
  37. ... print(f"{node}: {length[node]}")
  38. 0: 0
  39. 1: 1
  40. 2: 2
  41. 3: 3
  42. 4: 4
  43. See Also
  44. --------
  45. shortest_path_length
  46. """
  47. if source not in G:
  48. raise nx.NodeNotFound(f"Source {source} is not in G")
  49. if cutoff is None:
  50. cutoff = float("inf")
  51. nextlevel = [source]
  52. return dict(_single_shortest_path_length(G._adj, nextlevel, cutoff))
  53. def _single_shortest_path_length(adj, firstlevel, cutoff):
  54. """Yields (node, level) in a breadth first search
  55. Shortest Path Length helper function
  56. Parameters
  57. ----------
  58. adj : dict
  59. Adjacency dict or view
  60. firstlevel : list
  61. starting nodes, e.g. [source] or [target]
  62. cutoff : int or float
  63. level at which we stop the process
  64. """
  65. seen = set(firstlevel)
  66. nextlevel = firstlevel
  67. level = 0
  68. n = len(adj)
  69. for v in nextlevel:
  70. yield (v, level)
  71. while nextlevel and cutoff > level:
  72. level += 1
  73. thislevel = nextlevel
  74. nextlevel = []
  75. for v in thislevel:
  76. for w in adj[v]:
  77. if w not in seen:
  78. seen.add(w)
  79. nextlevel.append(w)
  80. yield (w, level)
  81. if len(seen) == n:
  82. return
  83. @nx._dispatch
  84. def single_target_shortest_path_length(G, target, cutoff=None):
  85. """Compute the shortest path lengths to target from all reachable nodes.
  86. Parameters
  87. ----------
  88. G : NetworkX graph
  89. target : node
  90. Target node for path
  91. cutoff : integer, optional
  92. Depth to stop the search. Only paths of length <= cutoff are returned.
  93. Returns
  94. -------
  95. lengths : iterator
  96. (source, shortest path length) iterator
  97. Examples
  98. --------
  99. >>> G = nx.path_graph(5, create_using=nx.DiGraph())
  100. >>> length = dict(nx.single_target_shortest_path_length(G, 4))
  101. >>> length[0]
  102. 4
  103. >>> for node in range(5):
  104. ... print(f"{node}: {length[node]}")
  105. 0: 4
  106. 1: 3
  107. 2: 2
  108. 3: 1
  109. 4: 0
  110. See Also
  111. --------
  112. single_source_shortest_path_length, shortest_path_length
  113. """
  114. if target not in G:
  115. raise nx.NodeNotFound(f"Target {target} is not in G")
  116. msg = "single_target_shortest_path_length will return a dict starting in v3.3"
  117. warnings.warn(msg, DeprecationWarning)
  118. if cutoff is None:
  119. cutoff = float("inf")
  120. # handle either directed or undirected
  121. adj = G._pred if G.is_directed() else G._adj
  122. nextlevel = [target]
  123. # for version 3.3 we will return a dict like this:
  124. # return dict(_single_shortest_path_length(adj, nextlevel, cutoff))
  125. return _single_shortest_path_length(adj, nextlevel, cutoff)
  126. @nx._dispatch
  127. def all_pairs_shortest_path_length(G, cutoff=None):
  128. """Computes the shortest path lengths between all nodes in `G`.
  129. Parameters
  130. ----------
  131. G : NetworkX graph
  132. cutoff : integer, optional
  133. Depth at which to stop the search. Only paths of length at most
  134. `cutoff` are returned.
  135. Returns
  136. -------
  137. lengths : iterator
  138. (source, dictionary) iterator with dictionary keyed by target and
  139. shortest path length as the key value.
  140. Notes
  141. -----
  142. The iterator returned only has reachable node pairs.
  143. Examples
  144. --------
  145. >>> G = nx.path_graph(5)
  146. >>> length = dict(nx.all_pairs_shortest_path_length(G))
  147. >>> for node in [0, 1, 2, 3, 4]:
  148. ... print(f"1 - {node}: {length[1][node]}")
  149. 1 - 0: 1
  150. 1 - 1: 0
  151. 1 - 2: 1
  152. 1 - 3: 2
  153. 1 - 4: 3
  154. >>> length[3][2]
  155. 1
  156. >>> length[2][2]
  157. 0
  158. """
  159. length = single_source_shortest_path_length
  160. # TODO This can be trivially parallelized.
  161. for n in G:
  162. yield (n, length(G, n, cutoff=cutoff))
  163. def bidirectional_shortest_path(G, source, target):
  164. """Returns a list of nodes in a shortest path between source and target.
  165. Parameters
  166. ----------
  167. G : NetworkX graph
  168. source : node label
  169. starting node for path
  170. target : node label
  171. ending node for path
  172. Returns
  173. -------
  174. path: list
  175. List of nodes in a path from source to target.
  176. Raises
  177. ------
  178. NetworkXNoPath
  179. If no path exists between source and target.
  180. Examples
  181. --------
  182. >>> G = nx.Graph()
  183. >>> nx.add_path(G, [0, 1, 2, 3, 0, 4, 5, 6, 7, 4])
  184. >>> nx.bidirectional_shortest_path(G, 2, 6)
  185. [2, 1, 0, 4, 5, 6]
  186. See Also
  187. --------
  188. shortest_path
  189. Notes
  190. -----
  191. This algorithm is used by shortest_path(G, source, target).
  192. """
  193. if source not in G or target not in G:
  194. msg = f"Either source {source} or target {target} is not in G"
  195. raise nx.NodeNotFound(msg)
  196. # call helper to do the real work
  197. results = _bidirectional_pred_succ(G, source, target)
  198. pred, succ, w = results
  199. # build path from pred+w+succ
  200. path = []
  201. # from source to w
  202. while w is not None:
  203. path.append(w)
  204. w = pred[w]
  205. path.reverse()
  206. # from w to target
  207. w = succ[path[-1]]
  208. while w is not None:
  209. path.append(w)
  210. w = succ[w]
  211. return path
  212. def _bidirectional_pred_succ(G, source, target):
  213. """Bidirectional shortest path helper.
  214. Returns (pred, succ, w) where
  215. pred is a dictionary of predecessors from w to the source, and
  216. succ is a dictionary of successors from w to the target.
  217. """
  218. # does BFS from both source and target and meets in the middle
  219. if target == source:
  220. return ({target: None}, {source: None}, source)
  221. # handle either directed or undirected
  222. if G.is_directed():
  223. Gpred = G.pred
  224. Gsucc = G.succ
  225. else:
  226. Gpred = G.adj
  227. Gsucc = G.adj
  228. # predecesssor and successors in search
  229. pred = {source: None}
  230. succ = {target: None}
  231. # initialize fringes, start with forward
  232. forward_fringe = [source]
  233. reverse_fringe = [target]
  234. while forward_fringe and reverse_fringe:
  235. if len(forward_fringe) <= len(reverse_fringe):
  236. this_level = forward_fringe
  237. forward_fringe = []
  238. for v in this_level:
  239. for w in Gsucc[v]:
  240. if w not in pred:
  241. forward_fringe.append(w)
  242. pred[w] = v
  243. if w in succ: # path found
  244. return pred, succ, w
  245. else:
  246. this_level = reverse_fringe
  247. reverse_fringe = []
  248. for v in this_level:
  249. for w in Gpred[v]:
  250. if w not in succ:
  251. succ[w] = v
  252. reverse_fringe.append(w)
  253. if w in pred: # found path
  254. return pred, succ, w
  255. raise nx.NetworkXNoPath(f"No path between {source} and {target}.")
  256. @nx._dispatch
  257. def single_source_shortest_path(G, source, cutoff=None):
  258. """Compute shortest path between source
  259. and all other nodes reachable from source.
  260. Parameters
  261. ----------
  262. G : NetworkX graph
  263. source : node label
  264. Starting node for path
  265. cutoff : integer, optional
  266. Depth to stop the search. Only paths of length <= cutoff are returned.
  267. Returns
  268. -------
  269. paths : dictionary
  270. Dictionary, keyed by target, of shortest paths.
  271. Examples
  272. --------
  273. >>> G = nx.path_graph(5)
  274. >>> path = nx.single_source_shortest_path(G, 0)
  275. >>> path[4]
  276. [0, 1, 2, 3, 4]
  277. Notes
  278. -----
  279. The shortest path is not necessarily unique. So there can be multiple
  280. paths between the source and each target node, all of which have the
  281. same 'shortest' length. For each target node, this function returns
  282. only one of those paths.
  283. See Also
  284. --------
  285. shortest_path
  286. """
  287. if source not in G:
  288. raise nx.NodeNotFound(f"Source {source} not in G")
  289. def join(p1, p2):
  290. return p1 + p2
  291. if cutoff is None:
  292. cutoff = float("inf")
  293. nextlevel = {source: 1} # list of nodes to check at next level
  294. paths = {source: [source]} # paths dictionary (paths to key from source)
  295. return dict(_single_shortest_path(G.adj, nextlevel, paths, cutoff, join))
  296. def _single_shortest_path(adj, firstlevel, paths, cutoff, join):
  297. """Returns shortest paths
  298. Shortest Path helper function
  299. Parameters
  300. ----------
  301. adj : dict
  302. Adjacency dict or view
  303. firstlevel : dict
  304. starting nodes, e.g. {source: 1} or {target: 1}
  305. paths : dict
  306. paths for starting nodes, e.g. {source: [source]}
  307. cutoff : int or float
  308. level at which we stop the process
  309. join : function
  310. function to construct a path from two partial paths. Requires two
  311. list inputs `p1` and `p2`, and returns a list. Usually returns
  312. `p1 + p2` (forward from source) or `p2 + p1` (backward from target)
  313. """
  314. level = 0 # the current level
  315. nextlevel = firstlevel
  316. while nextlevel and cutoff > level:
  317. thislevel = nextlevel
  318. nextlevel = {}
  319. for v in thislevel:
  320. for w in adj[v]:
  321. if w not in paths:
  322. paths[w] = join(paths[v], [w])
  323. nextlevel[w] = 1
  324. level += 1
  325. return paths
  326. @nx._dispatch
  327. def single_target_shortest_path(G, target, cutoff=None):
  328. """Compute shortest path to target from all nodes that reach target.
  329. Parameters
  330. ----------
  331. G : NetworkX graph
  332. target : node label
  333. Target node for path
  334. cutoff : integer, optional
  335. Depth to stop the search. Only paths of length <= cutoff are returned.
  336. Returns
  337. -------
  338. paths : dictionary
  339. Dictionary, keyed by target, of shortest paths.
  340. Examples
  341. --------
  342. >>> G = nx.path_graph(5, create_using=nx.DiGraph())
  343. >>> path = nx.single_target_shortest_path(G, 4)
  344. >>> path[0]
  345. [0, 1, 2, 3, 4]
  346. Notes
  347. -----
  348. The shortest path is not necessarily unique. So there can be multiple
  349. paths between the source and each target node, all of which have the
  350. same 'shortest' length. For each target node, this function returns
  351. only one of those paths.
  352. See Also
  353. --------
  354. shortest_path, single_source_shortest_path
  355. """
  356. if target not in G:
  357. raise nx.NodeNotFound(f"Target {target} not in G")
  358. def join(p1, p2):
  359. return p2 + p1
  360. # handle undirected graphs
  361. adj = G.pred if G.is_directed() else G.adj
  362. if cutoff is None:
  363. cutoff = float("inf")
  364. nextlevel = {target: 1} # list of nodes to check at next level
  365. paths = {target: [target]} # paths dictionary (paths to key from source)
  366. return dict(_single_shortest_path(adj, nextlevel, paths, cutoff, join))
  367. @nx._dispatch
  368. def all_pairs_shortest_path(G, cutoff=None):
  369. """Compute shortest paths between all nodes.
  370. Parameters
  371. ----------
  372. G : NetworkX graph
  373. cutoff : integer, optional
  374. Depth at which to stop the search. Only paths of length at most
  375. `cutoff` are returned.
  376. Returns
  377. -------
  378. paths : iterator
  379. Dictionary, keyed by source and target, of shortest paths.
  380. Examples
  381. --------
  382. >>> G = nx.path_graph(5)
  383. >>> path = dict(nx.all_pairs_shortest_path(G))
  384. >>> print(path[0][4])
  385. [0, 1, 2, 3, 4]
  386. See Also
  387. --------
  388. floyd_warshall
  389. """
  390. # TODO This can be trivially parallelized.
  391. for n in G:
  392. yield (n, single_source_shortest_path(G, n, cutoff=cutoff))
  393. def predecessor(G, source, target=None, cutoff=None, return_seen=None):
  394. """Returns dict of predecessors for the path from source to all nodes in G.
  395. Parameters
  396. ----------
  397. G : NetworkX graph
  398. source : node label
  399. Starting node for path
  400. target : node label, optional
  401. Ending node for path. If provided only predecessors between
  402. source and target are returned
  403. cutoff : integer, optional
  404. Depth to stop the search. Only paths of length <= cutoff are returned.
  405. return_seen : bool, optional (default=None)
  406. Whether to return a dictionary, keyed by node, of the level (number of
  407. hops) to reach the node (as seen during breadth-first-search).
  408. Returns
  409. -------
  410. pred : dictionary
  411. Dictionary, keyed by node, of predecessors in the shortest path.
  412. (pred, seen): tuple of dictionaries
  413. If `return_seen` argument is set to `True`, then a tuple of dictionaries
  414. is returned. The first element is the dictionary, keyed by node, of
  415. predecessors in the shortest path. The second element is the dictionary,
  416. keyed by node, of the level (number of hops) to reach the node (as seen
  417. during breadth-first-search).
  418. Examples
  419. --------
  420. >>> G = nx.path_graph(4)
  421. >>> list(G)
  422. [0, 1, 2, 3]
  423. >>> nx.predecessor(G, 0)
  424. {0: [], 1: [0], 2: [1], 3: [2]}
  425. >>> nx.predecessor(G, 0, return_seen=True)
  426. ({0: [], 1: [0], 2: [1], 3: [2]}, {0: 0, 1: 1, 2: 2, 3: 3})
  427. """
  428. if source not in G:
  429. raise nx.NodeNotFound(f"Source {source} not in G")
  430. level = 0 # the current level
  431. nextlevel = [source] # list of nodes to check at next level
  432. seen = {source: level} # level (number of hops) when seen in BFS
  433. pred = {source: []} # predecessor dictionary
  434. while nextlevel:
  435. level = level + 1
  436. thislevel = nextlevel
  437. nextlevel = []
  438. for v in thislevel:
  439. for w in G[v]:
  440. if w not in seen:
  441. pred[w] = [v]
  442. seen[w] = level
  443. nextlevel.append(w)
  444. elif seen[w] == level: # add v to predecessor list if it
  445. pred[w].append(v) # is at the correct level
  446. if cutoff and cutoff <= level:
  447. break
  448. if target is not None:
  449. if return_seen:
  450. if target not in pred:
  451. return ([], -1) # No predecessor
  452. return (pred[target], seen[target])
  453. else:
  454. if target not in pred:
  455. return [] # No predecessor
  456. return pred[target]
  457. else:
  458. if return_seen:
  459. return (pred, seen)
  460. else:
  461. return pred