Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/networkx/algorithms/traversal/breadth_first_search.py: 21%

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

116 statements  

1"""Basic algorithms for breadth-first searching the nodes of a graph.""" 

2 

3from collections import deque 

4 

5import networkx as nx 

6 

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] 

17 

18 

19@nx._dispatchable 

20def generic_bfs_edges(G, source, neighbors=None, depth_limit=None): 

21 """Iterate over edges in a breadth-first search. 

22 

23 The breadth-first search begins at `source` and enqueues the 

24 neighbors of newly visited nodes specified by the `neighbors` 

25 function. 

26 

27 Parameters 

28 ---------- 

29 G : NetworkX graph 

30 

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. 

35 

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. 

43 

44 depth_limit : int, optional(default=len(G)) 

45 Specify the maximum search depth. 

46 

47 Yields 

48 ------ 

49 edge 

50 Edges in the breadth-first search starting from `source`. 

51 

52 Examples 

53 -------- 

54 >>> G = nx.path_graph(7) 

55 >>> list(nx.generic_bfs_edges(G, source=0)) 

56 [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)] 

57 >>> list(nx.generic_bfs_edges(G, source=2)) 

58 [(2, 1), (2, 3), (1, 0), (3, 4), (4, 5), (5, 6)] 

59 >>> list(nx.generic_bfs_edges(G, source=2, depth_limit=2)) 

60 [(2, 1), (2, 3), (1, 0), (3, 4)] 

61 

62 The `neighbors` param can be used to specify the visitation order of each 

63 node's neighbors generically. In the following example, we modify the default 

64 neighbor to return *odd* nodes first: 

65 

66 >>> def odd_first(n): 

67 ... return sorted(G.neighbors(n), key=lambda x: x % 2, reverse=True) 

68 

69 >>> G = nx.star_graph(5) 

70 >>> list(nx.generic_bfs_edges(G, source=0)) # Default neighbor ordering 

71 [(0, 1), (0, 2), (0, 3), (0, 4), (0, 5)] 

72 >>> list(nx.generic_bfs_edges(G, source=0, neighbors=odd_first)) 

73 [(0, 1), (0, 3), (0, 5), (0, 2), (0, 4)] 

74 

75 Notes 

76 ----- 

77 This implementation is from `PADS`_, which was in the public domain 

78 when it was first accessed in July, 2004. The modifications 

79 to allow depth limits are based on the Wikipedia article 

80 "`Depth-limited-search`_". 

81 

82 .. _PADS: http://www.ics.uci.edu/~eppstein/PADS/BFS.py 

83 .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search 

84 """ 

85 if neighbors is None: 

86 neighbors = G.neighbors 

87 if depth_limit is None: 

88 depth_limit = len(G) 

89 

90 seen = {source} 

91 n = len(G) 

92 depth = 0 

93 next_parents_children = [(source, neighbors(source))] 

94 while next_parents_children and depth < depth_limit: 

95 this_parents_children = next_parents_children 

96 next_parents_children = [] 

97 for parent, children in this_parents_children: 

98 for child in children: 

99 if child not in seen: 

100 seen.add(child) 

101 next_parents_children.append((child, neighbors(child))) 

102 yield parent, child 

103 if len(seen) == n: 

104 return 

105 depth += 1 

106 

107 

108@nx._dispatchable 

109def bfs_edges(G, source, reverse=False, depth_limit=None, sort_neighbors=None): 

110 """Iterate over edges in a breadth-first-search starting at source. 

111 

112 Parameters 

113 ---------- 

114 G : NetworkX graph 

115 

116 source : node 

117 Specify starting node for breadth-first search; this function 

118 iterates over only those edges in the component reachable from 

119 this node. 

120 

121 reverse : bool, optional 

122 If True traverse a directed graph in the reverse direction 

123 

124 depth_limit : int, optional(default=len(G)) 

125 Specify the maximum search depth 

126 

127 sort_neighbors : function (default=None) 

128 A function that takes an iterator over nodes as the input, and 

129 returns an iterable of the same nodes with a custom ordering. 

130 For example, `sorted` will sort the nodes in increasing order. 

131 

132 Yields 

133 ------ 

134 edge: 2-tuple of nodes 

135 Yields edges resulting from the breadth-first search. 

136 

137 Examples 

138 -------- 

139 To get the edges in a breadth-first search:: 

140 

141 >>> G = nx.path_graph(3) 

142 >>> list(nx.bfs_edges(G, 0)) 

143 [(0, 1), (1, 2)] 

144 >>> list(nx.bfs_edges(G, source=0, depth_limit=1)) 

145 [(0, 1)] 

146 

147 To get the nodes in a breadth-first search order:: 

148 

149 >>> G = nx.path_graph(3) 

150 >>> root = 2 

151 >>> edges = nx.bfs_edges(G, root) 

152 >>> nodes = [root] + [v for u, v in edges] 

153 >>> nodes 

154 [2, 1, 0] 

155 

156 Notes 

157 ----- 

158 The naming of this function is very similar to 

159 :func:`~networkx.algorithms.traversal.edgebfs.edge_bfs`. The difference 

160 is that ``edge_bfs`` yields edges even if they extend back to an already 

161 explored node while this generator yields the edges of the tree that results 

162 from a breadth-first-search (BFS) so no edges are reported if they extend 

163 to already explored nodes. That means ``edge_bfs`` reports all edges while 

164 ``bfs_edges`` only reports those traversed by a node-based BFS. Yet another 

165 description is that ``bfs_edges`` reports the edges traversed during BFS 

166 while ``edge_bfs`` reports all edges in the order they are explored. 

167 

168 Based on the breadth-first search implementation in PADS [1]_ 

169 by D. Eppstein, July 2004; with modifications to allow depth limits 

170 as described in [2]_. 

171 

172 References 

173 ---------- 

174 .. [1] http://www.ics.uci.edu/~eppstein/PADS/BFS.py. 

175 .. [2] https://en.wikipedia.org/wiki/Depth-limited_search 

176 

177 See Also 

178 -------- 

179 bfs_tree 

180 :func:`~networkx.algorithms.traversal.depth_first_search.dfs_edges` 

181 :func:`~networkx.algorithms.traversal.edgebfs.edge_bfs` 

182 

183 """ 

184 if reverse and G.is_directed(): 

185 successors = G.predecessors 

186 else: 

187 successors = G.neighbors 

188 

189 if sort_neighbors is not None: 

190 yield from generic_bfs_edges( 

191 G, source, lambda node: iter(sort_neighbors(successors(node))), depth_limit 

192 ) 

193 else: 

194 yield from generic_bfs_edges(G, source, successors, depth_limit) 

195 

196 

197@nx._dispatchable(returns_graph=True) 

198def bfs_tree(G, source, reverse=False, depth_limit=None, sort_neighbors=None): 

199 """Returns an oriented tree constructed from of a breadth-first-search 

200 starting at source. 

201 

202 Parameters 

203 ---------- 

204 G : NetworkX graph 

205 

206 source : node 

207 Specify starting node for breadth-first search 

208 

209 reverse : bool, optional 

210 If True traverse a directed graph in the reverse direction 

211 

212 depth_limit : int, optional(default=len(G)) 

213 Specify the maximum search depth 

214 

215 sort_neighbors : function (default=None) 

216 A function that takes an iterator over nodes as the input, and 

217 returns an iterable of the same nodes with a custom ordering. 

218 For example, `sorted` will sort the nodes in increasing order. 

219 

220 Returns 

221 ------- 

222 T: NetworkX DiGraph 

223 An oriented tree 

224 

225 Examples 

226 -------- 

227 >>> G = nx.path_graph(3) 

228 >>> list(nx.bfs_tree(G, 1).edges()) 

229 [(1, 0), (1, 2)] 

230 >>> H = nx.Graph() 

231 >>> nx.add_path(H, [0, 1, 2, 3, 4, 5, 6]) 

232 >>> nx.add_path(H, [2, 7, 8, 9, 10]) 

233 >>> sorted(list(nx.bfs_tree(H, source=3, depth_limit=3).edges())) 

234 [(1, 0), (2, 1), (2, 7), (3, 2), (3, 4), (4, 5), (5, 6), (7, 8)] 

235 

236 

237 Notes 

238 ----- 

239 Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py 

240 by D. Eppstein, July 2004. The modifications 

241 to allow depth limits based on the Wikipedia article 

242 "`Depth-limited-search`_". 

243 

244 .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search 

245 

246 See Also 

247 -------- 

248 dfs_tree 

249 bfs_edges 

250 edge_bfs 

251 """ 

252 T = nx.DiGraph() 

253 T.add_node(source) 

254 edges_gen = bfs_edges( 

255 G, 

256 source, 

257 reverse=reverse, 

258 depth_limit=depth_limit, 

259 sort_neighbors=sort_neighbors, 

260 ) 

261 T.add_edges_from(edges_gen) 

262 return T 

263 

264 

265@nx._dispatchable 

266def bfs_predecessors(G, source, depth_limit=None, sort_neighbors=None): 

267 """Returns an iterator of predecessors in breadth-first-search from source. 

268 

269 Parameters 

270 ---------- 

271 G : NetworkX graph 

272 

273 source : node 

274 Specify starting node for breadth-first search 

275 

276 depth_limit : int, optional(default=len(G)) 

277 Specify the maximum search depth 

278 

279 sort_neighbors : function (default=None) 

280 A function that takes an iterator over nodes as the input, and 

281 returns an iterable of the same nodes with a custom ordering. 

282 For example, `sorted` will sort the nodes in increasing order. 

283 

284 Returns 

285 ------- 

286 pred: iterator 

287 (node, predecessor) iterator where `predecessor` is the predecessor of 

288 `node` in a breadth first search starting from `source`. 

289 

290 Examples 

291 -------- 

292 >>> G = nx.path_graph(3) 

293 >>> dict(nx.bfs_predecessors(G, 0)) 

294 {1: 0, 2: 1} 

295 >>> H = nx.Graph() 

296 >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)]) 

297 >>> dict(nx.bfs_predecessors(H, 0)) 

298 {1: 0, 2: 0, 3: 1, 4: 1, 5: 2, 6: 2} 

299 >>> M = nx.Graph() 

300 >>> nx.add_path(M, [0, 1, 2, 3, 4, 5, 6]) 

301 >>> nx.add_path(M, [2, 7, 8, 9, 10]) 

302 >>> sorted(nx.bfs_predecessors(M, source=1, depth_limit=3)) 

303 [(0, 1), (2, 1), (3, 2), (4, 3), (7, 2), (8, 7)] 

304 >>> N = nx.DiGraph() 

305 >>> nx.add_path(N, [0, 1, 2, 3, 4, 7]) 

306 >>> nx.add_path(N, [3, 5, 6, 7]) 

307 >>> sorted(nx.bfs_predecessors(N, source=2)) 

308 [(3, 2), (4, 3), (5, 3), (6, 5), (7, 4)] 

309 

310 Notes 

311 ----- 

312 Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py 

313 by D. Eppstein, July 2004. The modifications 

314 to allow depth limits based on the Wikipedia article 

315 "`Depth-limited-search`_". 

316 

317 .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search 

318 

319 See Also 

320 -------- 

321 bfs_tree 

322 bfs_edges 

323 edge_bfs 

324 """ 

325 for s, t in bfs_edges( 

326 G, source, depth_limit=depth_limit, sort_neighbors=sort_neighbors 

327 ): 

328 yield (t, s) 

329 

330 

331@nx._dispatchable 

332def bfs_successors(G, source, depth_limit=None, sort_neighbors=None): 

333 """Returns an iterator of successors in breadth-first-search from source. 

334 

335 Parameters 

336 ---------- 

337 G : NetworkX graph 

338 

339 source : node 

340 Specify starting node for breadth-first search 

341 

342 depth_limit : int, optional(default=len(G)) 

343 Specify the maximum search depth 

344 

345 sort_neighbors : function (default=None) 

346 A function that takes an iterator over nodes as the input, and 

347 returns an iterable of the same nodes with a custom ordering. 

348 For example, `sorted` will sort the nodes in increasing order. 

349 

350 Returns 

351 ------- 

352 succ: iterator 

353 (node, successors) iterator where `successors` is the non-empty list of 

354 successors of `node` in a breadth first search from `source`. 

355 To appear in the iterator, `node` must have successors. 

356 

357 Examples 

358 -------- 

359 >>> G = nx.path_graph(3) 

360 >>> dict(nx.bfs_successors(G, 0)) 

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

362 >>> H = nx.Graph() 

363 >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)]) 

364 >>> dict(nx.bfs_successors(H, 0)) 

365 {0: [1, 2], 1: [3, 4], 2: [5, 6]} 

366 >>> G = nx.Graph() 

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

368 >>> nx.add_path(G, [2, 7, 8, 9, 10]) 

369 >>> dict(nx.bfs_successors(G, source=1, depth_limit=3)) 

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

371 >>> G = nx.DiGraph() 

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

373 >>> dict(nx.bfs_successors(G, source=3)) 

374 {3: [4], 4: [5]} 

375 

376 Notes 

377 ----- 

378 Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py 

379 by D. Eppstein, July 2004.The modifications 

380 to allow depth limits based on the Wikipedia article 

381 "`Depth-limited-search`_". 

382 

383 .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search 

384 

385 See Also 

386 -------- 

387 bfs_tree 

388 bfs_edges 

389 edge_bfs 

390 """ 

391 parent = source 

392 children = [] 

393 for p, c in bfs_edges( 

394 G, source, depth_limit=depth_limit, sort_neighbors=sort_neighbors 

395 ): 

396 if p == parent: 

397 children.append(c) 

398 continue 

399 yield (parent, children) 

400 children = [c] 

401 parent = p 

402 yield (parent, children) 

403 

404 

405@nx._dispatchable 

406def bfs_layers(G, sources): 

407 """Returns an iterator of all the layers in breadth-first search traversal. 

408 

409 Parameters 

410 ---------- 

411 G : NetworkX graph 

412 A graph over which to find the layers using breadth-first search. 

413 

414 sources : node in `G` or iterable of nodes in `G` 

415 Specify starting nodes for single source or multiple sources 

416 breadth-first search. 

417 

418 Yields 

419 ------ 

420 layer : list of nodes 

421 Yields list of nodes at the same distance from `sources`. 

422 

423 Examples 

424 -------- 

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

426 >>> dict(enumerate(nx.bfs_layers(G, [0, 4]))) 

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

428 >>> H = nx.Graph() 

429 >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)]) 

430 >>> dict(enumerate(nx.bfs_layers(H, [1]))) 

431 {0: [1], 1: [0, 3, 4], 2: [2], 3: [5, 6]} 

432 >>> dict(enumerate(nx.bfs_layers(H, [1, 6]))) 

433 {0: [1, 6], 1: [0, 3, 4, 2], 2: [5]} 

434 """ 

435 if sources in G: 

436 sources = [sources] 

437 

438 visited = set(sources) 

439 current_layer = list(visited) 

440 

441 for source in current_layer: 

442 if source not in G: 

443 raise nx.NetworkXError(f"The node {source} is not in the graph.") 

444 

445 # this is basically BFS, except that the current layer only stores the nodes at 

446 # same distance from sources at each iteration 

447 while current_layer: 

448 yield current_layer 

449 next_layer = [] 

450 for node in current_layer: 

451 for child in G[node]: 

452 if child not in visited: 

453 visited.add(child) 

454 next_layer.append(child) 

455 current_layer = next_layer 

456 

457 

458REVERSE_EDGE = "reverse" 

459TREE_EDGE = "tree" 

460FORWARD_EDGE = "forward" 

461LEVEL_EDGE = "level" 

462 

463 

464@nx._dispatchable 

465def bfs_labeled_edges(G, sources): 

466 """Iterate over edges in a breadth-first search (BFS) labeled by type. 

467 

468 We generate triple of the form (*u*, *v*, *d*), where (*u*, *v*) is the 

469 edge being explored in the breadth-first search and *d* is one of the 

470 strings 'tree', 'forward', 'level', or 'reverse'. A 'tree' edge is one in 

471 which *v* is first discovered and placed into the layer below *u*. A 

472 'forward' edge is one in which *u* is on the layer above *v* and *v* has 

473 already been discovered. A 'level' edge is one in which both *u* and *v* 

474 occur on the same layer. A 'reverse' edge is one in which *u* is on a layer 

475 below *v*. 

476 

477 We emit each edge exactly once. In an undirected graph, 'reverse' edges do 

478 not occur, because each is discovered either as a 'tree' or 'forward' edge. 

479 

480 Parameters 

481 ---------- 

482 G : NetworkX graph 

483 A graph over which to find the layers using breadth-first search. 

484 

485 sources : node in `G` or list of nodes in `G` 

486 Starting nodes for single source or multiple sources breadth-first search 

487 

488 Yields 

489 ------ 

490 edges: generator 

491 A generator of triples (*u*, *v*, *d*) where (*u*, *v*) is the edge being 

492 explored and *d* is described above. 

493 

494 Examples 

495 -------- 

496 >>> G = nx.cycle_graph(4, create_using=nx.DiGraph) 

497 >>> list(nx.bfs_labeled_edges(G, 0)) 

498 [(0, 1, 'tree'), (1, 2, 'tree'), (2, 3, 'tree'), (3, 0, 'reverse')] 

499 >>> G = nx.complete_graph(3) 

500 >>> list(nx.bfs_labeled_edges(G, 0)) 

501 [(0, 1, 'tree'), (0, 2, 'tree'), (1, 2, 'level')] 

502 >>> list(nx.bfs_labeled_edges(G, [0, 1])) 

503 [(0, 1, 'level'), (0, 2, 'tree'), (1, 2, 'forward')] 

504 """ 

505 if sources in G: 

506 sources = [sources] 

507 

508 neighbors = G._adj 

509 directed = G.is_directed() 

510 visited = set() 

511 visit = visited.discard if directed else visited.add 

512 # We use visited in a negative sense, so the visited set stays empty for the 

513 # directed case and level edges are reported on their first occurrence in 

514 # the undirected case. Note our use of visited.discard -- this is built-in 

515 # thus somewhat faster than a python-defined def nop(x): pass 

516 depth = {s: 0 for s in sources} 

517 queue = deque(depth.items()) 

518 push = queue.append 

519 pop = queue.popleft 

520 while queue: 

521 u, du = pop() 

522 for v in neighbors[u]: 

523 if v not in depth: 

524 depth[v] = dv = du + 1 

525 push((v, dv)) 

526 yield u, v, TREE_EDGE 

527 else: 

528 dv = depth[v] 

529 if du == dv: 

530 if v not in visited: 

531 yield u, v, LEVEL_EDGE 

532 elif du < dv: 

533 yield u, v, FORWARD_EDGE 

534 elif directed: 

535 yield u, v, REVERSE_EDGE 

536 visit(u) 

537 

538 

539@nx._dispatchable 

540def descendants_at_distance(G, source, distance): 

541 """Returns all nodes at a fixed `distance` from `source` in `G`. 

542 

543 Parameters 

544 ---------- 

545 G : NetworkX graph 

546 A graph 

547 source : node in `G` 

548 distance : the distance of the wanted nodes from `source` 

549 

550 Returns 

551 ------- 

552 set() 

553 The descendants of `source` in `G` at the given `distance` from `source` 

554 

555 Examples 

556 -------- 

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

558 >>> nx.descendants_at_distance(G, 2, 2) 

559 {0, 4} 

560 >>> H = nx.DiGraph() 

561 >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)]) 

562 >>> nx.descendants_at_distance(H, 0, 2) 

563 {3, 4, 5, 6} 

564 >>> nx.descendants_at_distance(H, 5, 0) 

565 {5} 

566 >>> nx.descendants_at_distance(H, 5, 1) 

567 set() 

568 """ 

569 if source not in G: 

570 raise nx.NetworkXError(f"The node {source} is not in the graph.") 

571 

572 bfs_generator = nx.bfs_layers(G, source) 

573 for i, layer in enumerate(bfs_generator): 

574 if i == distance: 

575 return set(layer) 

576 return set()