Problem overview
Dijkstra(n, edges) receives a connected, undirected, weighted graph with vertices 0 to n - 1. Each edge is [u, v, weight] with a weight from 1 to 1000. It returns an array dist where dist[i] is the length of the cheapest path from vertex 0 to vertex i. A path's length is the sum of its edge weights.
For n = 5 and edges = [[0,1,4],[0,2,1],[2,1,2],[1,3,1],[2,3,5],[3,4,3]] the answer is [0, 3, 1, 4, 7]. Vertex 1 is cheaper to reach as 0 to 2 to 1, costing 1 + 2 = 3, than by the direct edge of weight 4.
Approach: always settle the closest unsettled vertex
With equal weights, plain BFS gives shortest paths. With varied weights it doesn't, because a path with fewer edges can cost more. Dijkstra's algorithm handles non-negative weights with a greedy rule: keep a best-known distance for every vertex, and repeatedly take the not-yet-finished vertex with the smallest known distance. Its distance is final, because any other route to it would have to pass through a vertex that is at least as far away, and weights can't be negative.
When a vertex is taken, relax its edges. For an edge to a neighbour v with weight w, if dist[u] + w is smaller than the current dist[v], record the better value.
The "take the smallest" step is a job for a priority queue. This lesson uses a plain List that is re-sorted on every iteration, which is simple and fast enough for at most 1000 vertices and 5000 edges. Rather than updating an existing entry when a shorter path appears, it adds a new entry and ignores outdated ones when they come out. This is called lazy deletion.
Step by step
- Create
adj, an array ofnlists ofint[], and give every slot a fresh list in a loop. A new array of reference types is filled withnull. - For each edge, add
{v, weight}to the list ofuand{u, weight}to the list ofv. The graph is undirected, so each edge is stored from both ends. - Create
distand fill it withint.MaxValueto mean "not reached", then setdist[0] = 0. - Create the working list
pqand add the pair{0, 0}, the distance first, then the vertex. Putting the distance first makes sorting by distance easy. - Loop while
pq.Count > 0. - Sort
pqby distance, take the first pair ascurand remove it withRemoveAt(0). Readdandu. Ifdis greater thandist[u], a better entry foruwas already processed, so skip this stale one. - Loop over the neighbours
nbofu. - Read
vandw. Ifdist[u] + w < dist[v], setdist[v]to that value and add{dist[v], v}to the list. Only improving paths are added, so the list doesn't fill with useless entries. - Return
dist.
The solution in C#
class Solution {
public int[] Dijkstra(int n, int[][] edges) {
var adj = new List<int[]>[n];
for (int i = 0; i < n; i++) {
adj[i] = new List<int[]>();
}
foreach (int[] e in edges) {
adj[e[0]].Add(new int[]{e[1], e[2]});
adj[e[1]].Add(new int[]{e[0], e[2]});
}
int[] dist = new int[n];
for (int i = 0; i < n; i++) {
dist[i] = int.MaxValue;
}
dist[0] = 0;
var pq = new List<int[]>();
pq.Add(new int[]{0, 0});
while (pq.Count > 0) {
pq.Sort((a, b) => a[0] - b[0]);
int[] cur = pq[0];
pq.RemoveAt(0);
int d = cur[0], u = cur[1];
if (d > dist[u]) {
continue;
}
foreach (int[] nb in adj[u]) {
int v = nb[0], w = nb[1];
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
pq.Add(new int[]{dist[v], v});
}
}
}
return dist;
}
}
Trace the example. The list starts as [(0, vertex 0)]. Taking vertex 0 sets dist[1] = 4 and dist[2] = 1. The smallest entry is now vertex 2 at distance 1. Its edges improve vertex 1 to 1 + 2 = 3 and set vertex 3 to 1 + 5 = 6. Next comes vertex 1 at distance 3, which improves vertex 3 to 4. The old entry for vertex 1 at distance 4 is still in the list. When it is taken, 4 > dist[1] (which is 3), so it is skipped. Vertex 3 at distance 4 then sets vertex 4 to 7, and the final array is [0, 3, 1, 4, 7].
Some C# details:
- Array of lists.
var adj = new List<int[]>[n];allocates only the array, so each element isnulluntil the loop assigns a list. Readingadj[i].Add(...)before that throwsNullReferenceException. - No overflow.
dist[u] + wis computed only for a vertexuthat has been reached, sodist[u]is a real distance and the sum can't overflow the wayint.MaxValue + wwould. In C#,intarithmetic wraps silently by default, which would give a large negative number. Sortwith a lambda.a[0] - b[0]orders pairs by distance, smallest first. The subtraction is safe because the distances are small.RemoveAt(0)removes the first element and shifts all the others down, which costs time proportional to the list's length.- A real priority queue. .NET has
PriorityQueue<TElement, TPriority>(inSystem.Collections.Generic). With it, the main loop becomeswhile (pq.TryDequeue(out int u, out int d)),pq.Enqueue(v, dist[v])adds an entry, and no sorting is needed.
Complexity
Let E be the number of edges. Every successful relaxation adds one entry, and there are at most 2E of those, since each edge is examined from both ends. With a binary-heap priority queue, the total time is O(E log V). This lesson's list sorts on every iteration, so each pass costs about O(P log P) for a list of P entries, and the total is higher. That is acceptable for the lesson's limits but not for large graphs.
Space: O(V + E) for the adjacency lists and the entries waiting in the queue.
Edge cases and common mistakes
- Parallel edges. If two edges join the same vertices with different weights, relaxation keeps the cheaper one. With
edges = [[0,1,7],[0,1,3]]the answer is[0, 3]. - A direct edge that isn't the cheapest. With
edges = [[1,0,5],[2,1,5],[0,2,20]]the answer is[0, 5, 10]. The direct edge to vertex 2 costs 20, and the route through vertex 1 costs only 10. - Forgetting the stale-entry check. The answer is still correct, since outdated entries can't improve anything. Without the check, you repeat work for no gain.
- Adding each edge in one direction. The graph is undirected, so both endpoints need the edge, or some vertices are unreachable.
- Negative weights. Dijkstra's algorithm assumes weights are never negative. The greedy claim "the smallest known distance is final" is false otherwise, and other algorithms such as Bellman-Ford are needed. This lesson guarantees weights from 1 to 1000.
- Starting distances at 0. If
distkeeps its default zeros, no relaxation ever succeeds. The sentinel must be larger than any real distance.
Testing
n = 5, the example's edges, gives[0, 3, 1, 4, 7].n = 2,edges = [[0,1,7],[0,1,3]]gives[0, 3].n = 3,edges = [[1,0,5],[2,1,5],[0,2,20]]gives[0, 5, 10].