Shortest Path (Edge List, BFS)
`hasPath` answers 'can I get there'; this answers 'how many edges does it take'. The only structural change from a plain BFS reachability check is what rides in the queue — `[node, distance]` pairs instead of bare nodes — so distance falls out of the traversal itself instead of needing a separate levels counter.
The problem
Implement `shortestPath(edges, source, target)` where `edges` is a list of `[a, b]` pairs describing an undirected graph. Return the number of edges on the shortest path from `source` to `target`, or `-1` if `target` isn't reachable. `source === target` is its own case, returning `0` with no traversal needed.
The edge list first has to become an adjacency list — the exact same `createGraphAdjacencyObjectFromEdgeList` from `undirectedPath`, unchanged.
The approach
BFS is the only traversal that gets distance for free: because it explores level by level, the first time `target` is reached is guaranteed to be via a shortest path, so there's no need to compare candidate paths — just return as soon as it's found.
The queue holds `[node, distanceFromSource]` tuples rather than bare nodes. Seeding it with `[source, 0]` and, for every neighbour pushed, enqueuing `[node, distanceFromSource + 1]`, means each queue entry already knows its own distance — no side table mapping nodes to depths, no counting how many nodes are at the current BFS 'level' before advancing.
Same early-return shape as `hasPath`: check `neighbour == target` before marking it visited or pushing it, and return `distanceFromSource + 1` immediately rather than waiting to pop it off the queue on a later iteration.
The solution
export function createGraphAdjacencyObjectFromEdgeList(edges) {
const graph = {}
for (let edge of edges) {
const [e1, e2] = edge
if (!(e1 in graph)) graph[e1] = []
if (!(e2 in graph)) graph[e2] = []
graph[e1].push(e2)
graph[e2].push(e1)
}
return graph
}
export function shortestPath(edges, source, target) {
const graph = createGraphAdjacencyObjectFromEdgeList(edges)
if (source == target) return 0
const visited = new Set()
const q = [[source, 0]]
while (q.length) {
const [curr, distanceFromSource] = q.shift()
for (let node of graph[curr]) {
if (!visited.has(node)) {
if (node == target) {
return distanceFromSource + 1
}
visited.add(node)
q.push([node, distanceFromSource + 1])
}
}
}
return -1
}Time O(V + E)Space O(V)