Files
troupe-standup/wizard-work/beam/bfs_shortest_path.py
T

67 lines
1.7 KiB
Python

"""BFS shortest path (unweighted) with deterministic path reconstruction.
Public API:
shortest_path(adj, src, dst) -> list[int] | None
Returns the vertex sequence from src to dst along a shortest path,
or None if dst is unreachable. src==dst returns [src].
shortest_distances(adj, src) -> dict[int, int]
Distance in edges from src to every reachable vertex.
"""
from collections import deque
def shortest_distances(adj, src):
if src not in adj:
# isolated source still reachable to itself
pass
dist = {src: 0}
q = deque([src])
while q:
u = q.popleft()
for v in adj.get(u, ()):
if v not in dist:
dist[v] = dist[u] + 1
q.append(v)
return dist
def shortest_path(adj, src, dst):
if src == dst:
if src in adj or src in _all_vertices(adj):
return [src]
# allow unknown isolated vertex only if explicitly present as key? We require vertex existence.
return None
if src not in _all_vertices(adj) or dst not in _all_vertices(adj):
return None
parent = {src: None}
q = deque([src])
found = False
while q:
u = q.popleft()
if u == dst:
found = True
break
for v in adj.get(u, ()):
if v not in parent:
parent[v] = u
q.append(v)
if dst not in parent:
return None
# reconstruct
path = []
cur = dst
while cur is not None:
path.append(cur)
cur = parent[cur]
path.reverse()
return path
def _all_vertices(adj):
s = set(adj.keys())
for u, nbrs in adj.items():
for v in nbrs:
s.add(v)
return s