dijkstra's algorithm

Work in Progress

Summary

Dijkstra’s algorithm

Time complexity

00112233111213

Modified dijkstra

0011620384016102-3268304-1650011620384-8160-3280-16001162-163-84-16160-3280-16001162-163-84-24160-3280-16

Invariant

  • with +ve weights only
  • at each iteration, the nodes that have been “settled” by the algorithm will have the correct shortest path
  • can support -ve weights at the cost of the invariant

for SSSDSP with +ve weight, we can terminate once the destination has been vsited

Concept

Algorithm

  • sort the verticies according to distance from the source
  • greedily relax outgoing edges from the vertex with the lowest distance
  • once a vertex has been “settled” assume it has the shortest path, do not relax it again
java
void dijkstra(int s) { // O(V log V + E log V) = O((V+E) log V)
	dist.set(s, 0);
	
	TreeSet<int[]> pq = new TreeSet<>(Comparator.comparingInt(x -> x[1])); // balanced BST, bineary heap works as well
	for (int u = 0; u < V; ++u) // enqueue all verticies -> O(V log V)
		pq.add(new int[] { u, dist.get(u) }); // add [ v, d ] -> O(log V)

	while (!pq.isEmpty()) { // every vertex, k neighbours are relaxed -> O(E log V)
		int[] min = pq.pollFirst(); // node to be relaxed
		int u = min[0];
		
		// present in VisuAlgo
		if (visited.get(u)) continue; // already settled, skip
		visited.set(u, true);
		
		for (int[] v_w : AL.get(u)) { // e = [ v, w ]
			int v = v_w[0];
			int w = v_w[1];
			
			// absent in VisuAlgo
			// if (visited.get(v)) continue; // already settled, skip
			
			if (dist.get(u) + w >= dist.get(v)) 
				continue; // not improving, skip
			pq.remove(new int[] { v, dist.get(v) }); // erase old [ v, d ] -> O(log V)
			dist.set(v, dist.get(u) + w); // relax operation
			pq.add(new int[] { v, dist.get(v) }); // enqueue better [ v, d ] -> O(log V)
		}
	}
}
001123334254115223241528310192627
curr | pq
_    | [0, 0], [1, INF], [2, INF], [3, INF], [4, INF], [5, INF]
0    | [1, 1], [2, 5], [3, INF], [4, INF], [5, INF]
1    | [4, 2], [2, 3], [3, 3], [5, INF]
4    | [2, 3], [3, 3], [5, 4]
2    | [3, 3], [5, 4]
3    | [5, 4]
5    | _

verticies are only visited once, which can cause problems when the first visit is not the shortest distance

Negative weights

  • alternate path might result in shorter distance
  • shorter distance is not propagated
  • 4-pan -ve weight: dijkstra(0)
001121033461110223-10534001121030461110223-10534
curr | pq
_    | [0, 0], [1, INF], [2, INF], [3, INF], [4, INF]
0    | [1, 1], [2, 10], [3, INF], [4, INF]
1    | [3, 3], [2, 10], [4, INF]
3    | [4, 6], [2, 10]
4    | [2, 10]
2    | [3, 0]
3    | _  <- already settled, VisuAlgo relaxes it but doesn't propagate further

slightly different implementations of the original dijkstra will result in different outcomes

  • include both/the first visited check -> incorrect SSSP and violates invariant
  • without the visited checks -> correct SSSP, nodes may be relaxed multiple times but invariant violated

Modified algorithm

  • to support negative weight
  • BFS with a priority queue(min weight) instead
  • deteriorates to PQ + Bellman-Ford
java
void dijkstra(int s) { // O(V log V + E log V) = O((V+E) log V)
	dist.set(s, 0);
	
	PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(x -> x[1])); 
	pq.offer(new int[] { s, 0 }); // only the source is added
	
	while (!pq.isEmpty()) { // every vertex and every edge are relaxed -> O((V+E) log V)
		int[] min = pq.poll(); // node to be relaxed
		int u = min[0];
		
		for (int[] v_w : AL.get(u)) { // e = [ v, w ]
			int v = v_w[0];
			int w = v_w[1];
			
			if (dist.get(u) + w >= dist.get(v)) 
				continue; // not improving, skip

			dist.set(v, dist.get(u) + w); // relax operation
			pq.offer(new int[] { v, dist.get(v) }); // enqueue better [ v, d ] -> O(log E) = O(log V^2) = O(log V)
		}
	}
}
001121033461110223-10534001121030431110223-10534
curr | pq
_    | [0, 0]
0    | [1, 1], [2, 10]
1    | [3, 3], [2, 10]
3    | [4, 6], [2, 10]
4    | [2, 10]
2    | [3, 0]
3    | [4, 3] <- propagated further
4    | _

correct SSSP but invariant is violated

Application

Leetcode: Network Delay Time

  • 1-indexing
  • max of all SSSPs
java
List<List<int[]>> AL = new ArrayList<>();
for (int i = 0; i <= n; i++) // 1-indexing
	AL.add(new ArrayList<>());

for (int[] e : times)
	AL.get(e[0]).add(new int[] { e[1], e[2] });

int INF = 1000000000;

List<Integer> dist = new ArrayList<>(Collections.nCopies(n + 1, INF));
dist.set(0, 0); // ignore the 0th node
dist.set(k, 0);

PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(x -> x[1]));
pq.offer(new int[] { k, 0 });

while (!pq.isEmpty()) {
	int[] min = pq.poll();
	int u = min[0];

	for (int[] v_w : AL.get(u)) { // e = [ v, w ]
		int v = v_w[0];
		int w = v_w[1];

		if (dist.get(u) + w >= dist.get(v))
			continue; // not improving, skip

		dist.set(v, dist.get(u) + w); // relax operation
		pq.offer(new int[] { v, dist.get(v) });
	}
}

Collections.sort(dist); // O(V log V)
int max = dist.get(n);
return max == INF ? -1 : max;

Kattis: Vacation Time

  • SSSP from source and destination, bidirectional search
  • meet in the middle with selected edge
java
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
PrintWriter pw = new PrintWriter(System.out);

StringTokenizer st = new StringTokenizer(br.readLine());

int a = Integer.parseInt(st.nextToken()), f = Integer.parseInt(st.nextToken());

ArrayList<ArrayList<int[]>> AL = new ArrayList<>();
ArrayList<ArrayList<int[]>> rev = new ArrayList<>();
ArrayList<int[]> edges = new ArrayList<>();
for (int u = 0; u < a; ++u) {
	AL.add(new ArrayList<>());
	rev.add(new ArrayList<>());
}

while (f-- > 0) {
	st = new StringTokenizer(br.readLine());
	int o = Integer.parseInt(st.nextToken()), d = Integer.parseInt(st.nextToken()),
			c = Integer.parseInt(st.nextToken());
	String m = st.nextToken();
	AL.get(o).add(new int[] { d, c });
	rev.get(d).add(new int[] { o, c });
	if (m.equals("A380"))
		edges.add(new int[] { o, d, c });
}

int INF = 1000000000;
ArrayList<Integer> distS = new ArrayList<>();
ArrayList<Integer> distT = new ArrayList<>();
for (int i = 0; i < a; i++) {
	distS.add(INF);
	distT.add(INF);
}
distS.set(0, 0); // start from 0
distT.set(a - 1, 0); // start from a - 1

PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(i -> i[0]));
pq.add(new int[] { 0, 0 }); // [dist, u]

while (!pq.isEmpty()) {
	int[] top = pq.poll();
	int u = top[1];
	for (int[] next : AL.get(u)) {
		int v = next[0], w = next[1];
		if (distS.get(u) + w >= distS.get(v))
			continue;
		distS.set(v, distS.get(u) + w);
		pq.add(new int[] { distS.get(v), v });
	}
}

pq.clear();
pq.add(new int[] { 0, a - 1 }); // [dist, u]

while (!pq.isEmpty()) {
	int[] top = pq.poll();
	int u = top[1];
	for (int[] next : rev.get(u)) {
		int v = next[0], w = next[1];
		if (distT.get(u) + w >= distT.get(v))
			continue;
		distT.set(v, distT.get(u) + w);
		pq.add(new int[] { distT.get(v), v });
	}
}

int min = INF;
for (int i = 0; i < edges.size(); i++) {
	int[] e = edges.get(i);
	min = Math.min(min, distS.get(e[0]) + distT.get(e[1]) + e[2]);
}

pw.println(min == INF ? -1 : min);
pw.close();

Leetcode: Minumum Time to Visit Disappearing Nodes

java
List<List<int[]>> AL = new ArrayList<>();
for (int i = 0; i < n; i++)
	AL.add(new ArrayList<>());

for (int[] e : edges) { // undirected edges
	AL.get(e[0]).add(new int[] { e[1], e[2] });
	AL.get(e[1]).add(new int[] { e[0], e[2] });
}

int INF = 1000000000;
int[] dist = new int[n];
Arrays.fill(dist, INF);
dist[0] = 0; // 0 is the source
// disappear[0] >= 1, so no edge case here

boolean[] visited = new boolean[n];
Arrays.fill(visited, false);

PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(x -> x[1]));
pq.offer(new int[] { 0, 0 });

while (!pq.isEmpty()) {
	int[] min = pq.poll();
	int u = min[0];

	if (visited[u]) // avoid revisiting nodes
		continue;
	visited[u] = true;

	if (dist[u] >= disappear[u])
		continue;

	for (int[] v_w : AL.get(u)) { // e = [ v, w ]
		int v = v_w[0];
		int w = v_w[1];

		int r = dist[u] + w;
		if (r >= disappear[v] || r >= dist[v]) // if the resulting distance is >= to the time it disappears
			continue; // not improving, skip

		dist[v] = r; // relax operation
		pq.offer(new int[] { v, dist[v] });
	}
}

for (int i = 0; i < n; i++)
	if (dist[i] == INF)
		dist[i] = -1;

return dist;
}

Leetcode: Number of Ways to Arrive at Destination

  • number of shortests paths
  • count paths modification
  • better solution using BFS
java
List<List<int[]>> AL = new ArrayList<>();
for (int i = 0; i < n; i++)
	AL.add(new ArrayList<>());

for (int[] e : roads) { // undirected edges
	AL.get(e[0]).add(new int[] { e[1], e[2] });
	AL.get(e[1]).add(new int[] { e[0], e[2] });
}

long INF = Long.MAX_VALUE;
long[] dist = new long[n];
Arrays.fill(dist, INF);
dist[0] = 0;

int[] ways = new int[n]; // keep track of number of ways to each node
Arrays.fill(ways, 0);
ways[0] = 1; // only 1 way to source

PriorityQueue<long[]> pq = new PriorityQueue<>(Comparator.comparingLong(x -> x[1]));
pq.offer(new long[] { 0, 0 });

while (!pq.isEmpty()) {
	long[] min = pq.poll();
	int u = (int) min[0];

	if (min[1] > dist[u]) // distance is not the mimimum, no point checking
		continue;

	for (int[] v_w : AL.get(u)) { // e = [ v, w ]
		int v = v_w[0];
		int w = v_w[1];

		if (v == n-1) System.out.println("here");

		if (dist[u] + w < dist[v]) {
			dist[v] = dist[u] + w; // relax operation
			ways[v] = ways[u]; // replace the number of ways
			pq.offer(new long[] { v, dist[v] });
		} else if (dist[u] + w == dist[v]) { // alr visited with same distance, add the ways
			ways[v] = (ways[v] + ways[u]) % (1000000000 + 7);
		}
	}
}
return ways[n - 1];

Leetcode: Cheapest Flights Within K Stops

  • SSSP by jumps, accumulate distance
  • early termination once destination is reached
java
List<List<int[]>> AL = new ArrayList<>();
for (int i = 0; i < n; i++)
	AL.add(new ArrayList<>());

for (int[] e : flights) // directed edges
	AL.get(e[0]).add(new int[] { e[1], e[2] });

int INF = 1000000000;
int[] jmps = new int[n];
Arrays.fill(jmps, INF);
jmps[src] = 0;

PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(x -> x[1]));
pq.offer(new int[] { src, 0, 0 });

while (!pq.isEmpty()) {
	int[] min = pq.poll();
	int u = min[0];
	int d = min[1];
	int j = min[2];

	if (u == dst)
		return min[1]; // early termination

	// overall least jumps, SSSP by jumps
	if (j > k || j > jmps[u]) // skip if more than k jumps are needed or there is a path with less jumps to this node
		continue;

	jmps[u] = j;

	for (int[] v_w : AL.get(u)) { // e = [ v, w ]
		int v = v_w[0];
		int w = v_w[1];

		//don't care about getting the overall shortest path
		pq.offer(new int[] { v, min[1] + w, j + 1 }); // implicit relax operation
	}
}

return -1;

Extra