67 lines
1.7 KiB
Python
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
|