Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/networkx/algorithms/shortest_paths/unweighted.py: 14%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

170 statements  

1""" 

2Shortest path algorithms for unweighted graphs. 

3""" 

4 

5import operator 

6 

7import networkx as nx 

8 

9__all__ = [ 

10 "bidirectional_shortest_path", 

11 "single_source_shortest_path", 

12 "single_source_shortest_path_length", 

13 "single_target_shortest_path", 

14 "single_target_shortest_path_length", 

15 "all_pairs_shortest_path", 

16 "all_pairs_shortest_path_length", 

17 "predecessor", 

18] 

19 

20 

21@nx._dispatchable 

22def single_source_shortest_path_length(G, source, cutoff=None): 

23 """Compute the shortest path lengths from `source` to all reachable nodes in `G`. 

24 

25 Parameters 

26 ---------- 

27 G : NetworkX graph 

28 

29 source : node 

30 Starting node for path 

31 

32 cutoff : integer, optional 

33 Depth to stop the search. Only target nodes where the shortest path to 

34 this node from the source node contains <= ``cutoff + 1`` nodes will be 

35 included in the returned results. 

36 

37 Returns 

38 ------- 

39 lengths : dict 

40 Dict keyed by node to shortest path length from source node. 

41 

42 Examples 

43 -------- 

44 >>> G = nx.path_graph(5) 

45 >>> length = nx.single_source_shortest_path_length(G, 0) 

46 >>> length[4] 

47 4 

48 >>> for node in sorted(length): 

49 ... print(f"{node}: {length[node]}") 

50 0: 0 

51 1: 1 

52 2: 2 

53 3: 3 

54 4: 4 

55 

56 Only include paths with length less than or equal to the `cutoff` keyword 

57 argument: 

58 

59 >>> length = nx.single_source_shortest_path_length(G, 0, cutoff=2) 

60 >>> for node in sorted(length): 

61 ... print(f"{node}: {length[node]}") 

62 0: 0 

63 1: 1 

64 2: 2 

65 

66 See Also 

67 -------- 

68 :any:`shortest_path_length` : 

69 Shortest path length with specifiable source, target, and weight. 

70 :any:`single_source_dijkstra_path_length` : 

71 Shortest weighted path length from source with Dijkstra algorithm. 

72 :any:`single_source_bellman_ford_path_length` : 

73 Shortest weighted path length from source with Bellman-Ford algorithm. 

74 """ 

75 if source not in G: 

76 raise nx.NodeNotFound(f"Source {source} is not in G") 

77 if cutoff is None: 

78 cutoff = float("inf") 

79 nextlevel = [source] 

80 return dict(_single_shortest_path_length(G._adj, nextlevel, cutoff)) 

81 

82 

83def _single_shortest_path_length(adj, firstlevel, cutoff): 

84 """Yields (node, level) in a breadth first search 

85 

86 Shortest Path Length helper function 

87 Parameters 

88 ---------- 

89 adj : dict 

90 Adjacency dict or view 

91 firstlevel : list 

92 starting nodes, e.g. [source] or [target] 

93 cutoff : int or float 

94 level at which we stop the process 

95 """ 

96 seen = set(firstlevel) 

97 nextlevel = firstlevel 

98 level = 0 

99 n = len(adj) 

100 for v in nextlevel: 

101 yield (v, level) 

102 while nextlevel and cutoff > level: 

103 level += 1 

104 thislevel = nextlevel 

105 nextlevel = [] 

106 for v in thislevel: 

107 for w in adj[v]: 

108 if w not in seen: 

109 seen.add(w) 

110 nextlevel.append(w) 

111 yield (w, level) 

112 if len(seen) == n: 

113 return 

114 

115 

116@nx._dispatchable 

117def single_target_shortest_path_length(G, target, cutoff=None): 

118 """Compute the shortest path lengths to target from all reachable nodes. 

119 

120 Parameters 

121 ---------- 

122 G : NetworkX graph 

123 

124 target : node 

125 Target node for path 

126 

127 cutoff : integer, optional 

128 Depth to stop the search. Only source nodes where the shortest path 

129 from this node to the target node contains <= ``cutoff + 1`` nodes will 

130 be included in the returned results. 

131 

132 Returns 

133 ------- 

134 lengths : dictionary 

135 Dictionary, keyed by source, of shortest path lengths. 

136 

137 Examples 

138 -------- 

139 >>> G = nx.path_graph(5, create_using=nx.DiGraph()) 

140 >>> length = nx.single_target_shortest_path_length(G, 4) 

141 >>> length[0] 

142 4 

143 >>> for node in sorted(length): 

144 ... print(f"{node}: {length[node]}") 

145 0: 4 

146 1: 3 

147 2: 2 

148 3: 1 

149 4: 0 

150 

151 Only include paths with length less than or equal to the `cutoff` keyword 

152 argument: 

153 

154 >>> length = nx.single_target_shortest_path_length(G, 4, cutoff=2) 

155 >>> for node in sorted(length): 

156 ... print(f"{node}: {length[node]}") 

157 2: 2 

158 3: 1 

159 4: 0 

160 

161 See Also 

162 -------- 

163 single_source_shortest_path_length, shortest_path_length 

164 """ 

165 if target not in G: 

166 raise nx.NodeNotFound(f"Target {target} is not in G") 

167 if cutoff is None: 

168 cutoff = float("inf") 

169 # handle either directed or undirected 

170 adj = G._pred if G.is_directed() else G._adj 

171 nextlevel = [target] 

172 return dict(_single_shortest_path_length(adj, nextlevel, cutoff)) 

173 

174 

175@nx._dispatchable 

176def all_pairs_shortest_path_length(G, cutoff=None): 

177 """Computes the shortest path lengths between all nodes in `G`. 

178 

179 Parameters 

180 ---------- 

181 G : NetworkX graph 

182 

183 cutoff : integer, optional 

184 Depth at which to stop the search. Only paths of length at most 

185 `cutoff` (i.e. paths containing <= ``cutoff + 1`` nodes) are returned. 

186 

187 Returns 

188 ------- 

189 lengths : iterator 

190 (source, dictionary) iterator with dictionary keyed by target and 

191 shortest path length as the key value. 

192 

193 Notes 

194 ----- 

195 The iterator returned only has reachable node pairs. 

196 

197 Examples 

198 -------- 

199 >>> G = nx.path_graph(5) 

200 >>> length = dict(nx.all_pairs_shortest_path_length(G)) 

201 >>> for node in [0, 1, 2, 3, 4]: 

202 ... print(f"1 - {node}: {length[1][node]}") 

203 1 - 0: 1 

204 1 - 1: 0 

205 1 - 2: 1 

206 1 - 3: 2 

207 1 - 4: 3 

208 >>> length[3][2] 

209 1 

210 >>> length[2][2] 

211 0 

212 

213 Only include paths with length less than or equal to the `cutoff` keyword 

214 argument: 

215 

216 >>> path_lengths = dict(nx.all_pairs_shortest_path_length(G, cutoff=2)) 

217 >>> path_lengths[1] # node 4 is too far away to appear 

218 {1: 0, 0: 1, 2: 1, 3: 2} 

219 """ 

220 length = single_source_shortest_path_length 

221 # TODO This can be trivially parallelized. 

222 for n in G: 

223 yield (n, length(G, n, cutoff=cutoff)) 

224 

225 

226@nx._dispatchable 

227def bidirectional_shortest_path(G, source, target): 

228 """Returns a list of nodes in a shortest path between source and target. 

229 

230 Parameters 

231 ---------- 

232 G : NetworkX graph 

233 

234 source : node label 

235 starting node for path 

236 

237 target : node label 

238 ending node for path 

239 

240 Returns 

241 ------- 

242 path: list 

243 List of nodes in a path from source to target. 

244 

245 Raises 

246 ------ 

247 NetworkXNoPath 

248 If no path exists between source and target. 

249 

250 Examples 

251 -------- 

252 >>> G = nx.Graph() 

253 >>> nx.add_path(G, [0, 1, 2, 3, 0, 4, 5, 6, 7, 4]) 

254 >>> nx.bidirectional_shortest_path(G, 2, 6) 

255 [2, 1, 0, 4, 5, 6] 

256 

257 See Also 

258 -------- 

259 shortest_path 

260 

261 Notes 

262 ----- 

263 This algorithm is used by shortest_path(G, source, target). 

264 """ 

265 

266 if source not in G: 

267 raise nx.NodeNotFound(f"Source {source} is not in G") 

268 

269 if target not in G: 

270 raise nx.NodeNotFound(f"Target {target} is not in G") 

271 

272 # call helper to do the real work 

273 results = _bidirectional_pred_succ(G, source, target) 

274 pred, succ, w = results 

275 

276 # build path from pred+w+succ 

277 path = [] 

278 # from source to w 

279 while w is not None: 

280 path.append(w) 

281 w = pred[w] 

282 path.reverse() 

283 # from w to target 

284 w = succ[path[-1]] 

285 while w is not None: 

286 path.append(w) 

287 w = succ[w] 

288 

289 return path 

290 

291 

292def _bidirectional_pred_succ(G, source, target): 

293 """Bidirectional shortest path helper. 

294 

295 Returns (pred, succ, w) where 

296 pred is a dictionary of predecessors from w to the source, and 

297 succ is a dictionary of successors from w to the target. 

298 """ 

299 # does BFS from both source and target and meets in the middle 

300 if target == source: 

301 return ({target: None}, {source: None}, source) 

302 

303 # handle either directed or undirected 

304 if G.is_directed(): 

305 Gpred = G.pred 

306 Gsucc = G.succ 

307 else: 

308 Gpred = G.adj 

309 Gsucc = G.adj 

310 

311 # predecessor and successors in search 

312 pred = {source: None} 

313 succ = {target: None} 

314 

315 # initialize fringes, start with forward 

316 forward_fringe = [source] 

317 reverse_fringe = [target] 

318 

319 while forward_fringe and reverse_fringe: 

320 if len(forward_fringe) <= len(reverse_fringe): 

321 this_level = forward_fringe 

322 forward_fringe = [] 

323 for v in this_level: 

324 for w in Gsucc[v]: 

325 if w not in pred: 

326 forward_fringe.append(w) 

327 pred[w] = v 

328 if w in succ: # path found 

329 return pred, succ, w 

330 else: 

331 this_level = reverse_fringe 

332 reverse_fringe = [] 

333 for v in this_level: 

334 for w in Gpred[v]: 

335 if w not in succ: 

336 succ[w] = v 

337 reverse_fringe.append(w) 

338 if w in pred: # found path 

339 return pred, succ, w 

340 

341 raise nx.NetworkXNoPath(f"No path between {source} and {target}.") 

342 

343 

344@nx._dispatchable 

345def single_source_shortest_path(G, source, cutoff=None): 

346 """Compute shortest path between source 

347 and all other nodes reachable from source. 

348 

349 Parameters 

350 ---------- 

351 G : NetworkX graph 

352 

353 source : node label 

354 Starting node for path 

355 

356 cutoff : integer, optional 

357 Depth to stop the search. Only target nodes where the shortest path to 

358 this node from the source node contains <= ``cutoff + 1`` nodes will be 

359 included in the returned results. 

360 

361 Returns 

362 ------- 

363 paths : dictionary 

364 Dictionary, keyed by target, of shortest paths. 

365 

366 Examples 

367 -------- 

368 >>> G = nx.path_graph(5) 

369 >>> nx.single_source_shortest_path(G, 0) 

370 {0: [0], 1: [0, 1], 2: [0, 1, 2], 3: [0, 1, 2, 3], 4: [0, 1, 2, 3, 4]} 

371 

372 Only include paths with length less than or equal to the `cutoff` keyword 

373 argument: 

374 

375 >>> nx.single_source_shortest_path(G, 0, cutoff=2) 

376 {0: [0], 1: [0, 1], 2: [0, 1, 2]} 

377 

378 Notes 

379 ----- 

380 The shortest path is not necessarily unique. So there can be multiple 

381 paths between the source and each target node, all of which have the 

382 same 'shortest' length. For each target node, this function returns 

383 only one of those paths. 

384 

385 See Also 

386 -------- 

387 shortest_path 

388 """ 

389 if source not in G: 

390 raise nx.NodeNotFound(f"Source {source} not in G") 

391 if cutoff is None: 

392 cutoff = float("inf") 

393 nextlevel = [source] # list of nodes to check at next level 

394 paths = {source: [source]} # paths dictionary (paths to key from source) 

395 return dict(_single_shortest_path(G._adj, nextlevel, paths, cutoff, operator.add)) 

396 

397 

398def _single_shortest_path(adj, firstlevel, paths, cutoff, join): 

399 """Returns shortest paths 

400 

401 Shortest Path helper function 

402 Parameters 

403 ---------- 

404 adj : dict 

405 Adjacency dict or view 

406 firstlevel : list 

407 starting nodes, e.g. [source] or [target] 

408 paths : dict 

409 paths for starting nodes, e.g. {source: [source]} 

410 cutoff : int or float 

411 level at which we stop the process 

412 join : function 

413 function to construct a path from two partial paths. Requires two 

414 list inputs `p1` and `p2`, and returns a list. Usually returns 

415 `p1 + p2` (forward from source) or `p2 + p1` (backward from target) 

416 """ 

417 level = 0 

418 nextlevel = firstlevel 

419 n = len(adj) 

420 while nextlevel and cutoff > level: 

421 thislevel = nextlevel 

422 nextlevel = [] 

423 for v in thislevel: 

424 for w in adj[v]: 

425 if w not in paths: 

426 paths[w] = join(paths[v], [w]) 

427 nextlevel.append(w) 

428 if len(paths) == n: 

429 return paths 

430 level += 1 

431 return paths 

432 

433 

434@nx._dispatchable 

435def single_target_shortest_path(G, target, cutoff=None): 

436 """Compute shortest path to target from all nodes that reach target. 

437 

438 Parameters 

439 ---------- 

440 G : NetworkX graph 

441 

442 target : node label 

443 Target node for path 

444 

445 cutoff : integer, optional 

446 Depth to stop the search. Only source nodes where the shortest path 

447 from this node to the target node contains <= ``cutoff + 1`` nodes will 

448 be included in the returned results. 

449 

450 Returns 

451 ------- 

452 paths : dictionary 

453 Dictionary, keyed by source, of shortest paths. 

454 

455 Examples 

456 -------- 

457 >>> G = nx.path_graph(5, create_using=nx.DiGraph()) 

458 >>> nx.single_target_shortest_path(G, 4) 

459 {4: [4], 3: [3, 4], 2: [2, 3, 4], 1: [1, 2, 3, 4], 0: [0, 1, 2, 3, 4]} 

460 

461 Only include paths with length less than or equal to the `cutoff` keyword 

462 argument: 

463 

464 >>> nx.single_target_shortest_path(G, 4, cutoff=2) 

465 {4: [4], 3: [3, 4], 2: [2, 3, 4]} 

466 

467 Notes 

468 ----- 

469 The shortest path is not necessarily unique. So there can be multiple 

470 paths between the source and each target node, all of which have the 

471 same 'shortest' length. For each target node, this function returns 

472 only one of those paths. 

473 

474 See Also 

475 -------- 

476 shortest_path, single_source_shortest_path 

477 """ 

478 if target not in G: 

479 raise nx.NodeNotFound(f"Target {target} not in G") 

480 

481 def join(p1, p2): 

482 return p2 + p1 

483 

484 # handle undirected graphs 

485 adj = G._pred if G.is_directed() else G._adj 

486 if cutoff is None: 

487 cutoff = float("inf") 

488 nextlevel = [target] # list of nodes to check at next level 

489 paths = {target: [target]} # paths dictionary (paths to key from source) 

490 return dict(_single_shortest_path(adj, nextlevel, paths, cutoff, join)) 

491 

492 

493@nx._dispatchable 

494def all_pairs_shortest_path(G, cutoff=None): 

495 """Compute shortest paths between all nodes. 

496 

497 Parameters 

498 ---------- 

499 G : NetworkX graph 

500 

501 cutoff : integer, optional 

502 Depth at which to stop the search. Only paths containing at most 

503 ``cutoff + 1`` nodes are returned. 

504 

505 Returns 

506 ------- 

507 paths : iterator 

508 Dictionary, keyed by source and target, of shortest paths. 

509 

510 Examples 

511 -------- 

512 >>> G = nx.path_graph(5) 

513 >>> path = dict(nx.all_pairs_shortest_path(G)) 

514 >>> print(path[0]) 

515 {0: [0], 1: [0, 1], 2: [0, 1, 2], 3: [0, 1, 2, 3], 4: [0, 1, 2, 3, 4]} 

516 

517 Only include paths with length less than or equal to the `cutoff` keyword 

518 argument: 

519 

520 >>> path = dict(nx.all_pairs_shortest_path(G, cutoff=2)) 

521 >>> print(path[0]) 

522 {0: [0], 1: [0, 1], 2: [0, 1, 2]} 

523 

524 Notes 

525 ----- 

526 There may be multiple shortest paths with the same length between 

527 two nodes. For each pair, this function returns only one of those paths. 

528 

529 See Also 

530 -------- 

531 floyd_warshall 

532 all_pairs_all_shortest_paths 

533 

534 """ 

535 # TODO This can be trivially parallelized. 

536 for n in G: 

537 yield (n, single_source_shortest_path(G, n, cutoff=cutoff)) 

538 

539 

540@nx._dispatchable 

541def predecessor(G, source, target=None, cutoff=None, return_seen=None): 

542 """Returns dict of predecessors for the path from source to all nodes in G. 

543 

544 Parameters 

545 ---------- 

546 G : NetworkX graph 

547 

548 source : node label 

549 Starting node for path 

550 

551 target : node label, optional 

552 Ending node for path. If provided only predecessors between 

553 source and target are returned 

554 

555 cutoff : integer, optional 

556 Depth to stop the search. Only paths of length <= `cutoff` are 

557 returned. 

558 

559 return_seen : bool, optional (default=None) 

560 Whether to return a dictionary, keyed by node, of the level (number of 

561 hops) to reach the node (as seen during breadth-first-search). 

562 

563 Returns 

564 ------- 

565 pred : dictionary 

566 Dictionary, keyed by node, of predecessors in the shortest path. 

567 

568 

569 (pred, seen): tuple of dictionaries 

570 If `return_seen` argument is set to `True`, then a tuple of dictionaries 

571 is returned. The first element is the dictionary, keyed by node, of 

572 predecessors in the shortest path. The second element is the dictionary, 

573 keyed by node, of the level (number of hops) to reach the node (as seen 

574 during breadth-first-search). 

575 

576 Examples 

577 -------- 

578 >>> G = nx.path_graph(4) 

579 >>> list(G) 

580 [0, 1, 2, 3] 

581 >>> nx.predecessor(G, 0) 

582 {0: [], 1: [0], 2: [1], 3: [2]} 

583 >>> nx.predecessor(G, 0, cutoff=2) 

584 {0: [], 1: [0], 2: [1]} 

585 >>> nx.predecessor(G, 0, return_seen=True) 

586 ({0: [], 1: [0], 2: [1], 3: [2]}, {0: 0, 1: 1, 2: 2, 3: 3}) 

587 

588 

589 """ 

590 if source not in G: 

591 raise nx.NodeNotFound(f"Source {source} not in G") 

592 

593 level = 0 # the current level 

594 nextlevel = [source] # list of nodes to check at next level 

595 seen = {source: level} # level (number of hops) when seen in BFS 

596 pred = {source: []} # predecessor dictionary 

597 while nextlevel: 

598 level = level + 1 

599 thislevel = nextlevel 

600 nextlevel = [] 

601 for v in thislevel: 

602 for w in G[v]: 

603 if w not in seen: 

604 pred[w] = [v] 

605 seen[w] = level 

606 nextlevel.append(w) 

607 elif seen[w] == level: # add v to predecessor list if it 

608 pred[w].append(v) # is at the correct level 

609 if cutoff and cutoff <= level: 

610 break 

611 

612 if target is not None: 

613 if return_seen: 

614 if target not in pred: 

615 return ([], -1) # No predecessor 

616 return (pred[target], seen[target]) 

617 else: 

618 if target not in pred: 

619 return [] # No predecessor 

620 return pred[target] 

621 else: 

622 if return_seen: 

623 return (pred, seen) 

624 else: 

625 return pred