L4: Graph Algorithms II
In this lab we consider the remaining the problems from Jutge.org, section EDA \rightarrow Graph Algorithms. The problems in this section we see in this lab of two different types
- variations on Dijkstra’s algorithm,
- variations on Prim’s algorithm.
Dijkstra’s Algorithm
Given a weighted directed graph G= (V,E), Dijkstra’s algorithm (1959) finds all shortest paths from a given vertex (the source) to all other vertices in the directed graph. The weights on the edges are given by a function w\colon E\to \mathbb R^+.
Given two vertices u,v in G, their distance1 d_G(u,v) in the weighted directed graph G is the minimum weight of a path from u to v, where the weight of a path is taken as the sum of the weights of the edges in it.
Dijkstra’s algorithm works iteratively. The input is a weighted directed graph G=(V,E) and a vertex s. The algorithm will return a vector D, where D[u] is the minimum distance of u from the starting point s.
On each iteration the set of vertices is partitioned in two parts: the vertices that we have visited and those that haven’t been visited yet, also called candidates. The invariant of the loop establishes that
- For all visited vertices u, D[u] is the minimum weight of a path from s to u,
- For all candidate vertices u, D[u] is the weight of the shortest path from s to u which passes exclusively through visited vertices, i.e., only the last vertex in the path, u, is a candidate.
Each iteration of the main loop selects a candidate vertex u, updates D and marks u as visited. When all vertices have been visited, we have the desired answer.
Which candidate vertex do we need to select on each iteration of Dijkstra’s algorithm?
Intuitively, the candidate to choose is the one with minimal D. Let u be that vertex. According to the invariant, D[u] is minimal among all paths passing only through visited vertices. But it must also be the minimal weight for all paths. If there were a shortest path going through a candidate vertex x, the weight of that path would be D[x] + d_G(x,u) < D[u]. Since d_G(x,u) \geq 0 we would have D[x] < D[u], and that’s a contradiction since by assumption D[u] is minimal among all candidate vertices.
As we have just seen D[v] is the minimum weight of a path from s to v, for all visited vertices v, and for the vertex u to be visited.
We need now to consider how to maintain the second part of the invariant. Consider a candidate vertex v. Since u becomes a visited vertex in the current iteration, we may have a new shortest path from s to v going only through visited vertices that now include u. A simple reasoning shows that if such shortest path includes u then u must be the immediate predecessor of v in the path (assuming the contrary leads to a contradiction).
It follows that D[v] can change if and only if v is a successor of u; in particular, if D[v] >D[u] + w\ , where w is the weight of the edge (u,v), then D[v] must be updated D[v] := D[u] + w. The condition above is never true if v has already been visited.
See an animation of a run of Dijkstra’s algorithm!
Or also this animation from wikipedia:
Jutge exercises
The Jutge exercises that can be thought as variations on Dijkstra’s algorithm are the following:
- P43859 Weighted shortest path (1) 🔶
- P13994 Weighted shortest path (2)
- P25235 Weighted shortest path (3)
- P39586 Weighted shortest path (4) ❗
Once you have chosen a problem to solve, download the .zip archive of the problem and unzip
it. In the obtained folder there are:
- a file .pdf with the official problem statement
- one or more text files with examples of input
sample.inp
- the corresponding correct outputs
sample.cor
After creating a C++ source file to solve the problem, compile it with g++ -std=c++11
.
Not using the flag -std=c++11
.
Possible useful flags are -Wall -Wextra -Wno-overflow -Wpedantic -Werror -D_GLIBCXX_DEBUG
Test the executable file obtained on the given example(s), for instance with ./a.out < sample.inp > output
. Also, test your code against simple/trivial inputs, e.g. a graph with no-edges.
Writing the input by hand.
Not checking the exact formatting of the output.
To see whether the output
is the same as sample.cor
a way is to use diff output sample.cor
Once the code compiles and on the given samples it gives the correct output submit it to the Jutge for automatic evaluation.
Sending to the Jutge code that was not even compiled locally.
Not checking if the output was correct on the given inputs.
You are “stuck” on a problem if you spent around 1 hour trying to solve it but still the Jutge is rejecting your solution.
Possible approaches to getting “un-stuck” are:
do not think about that problem for some time and come back to your code with fresh eyes
look again at the theory
try to explain your code to another student
ask for hints, e.g. by email. Be specific on the type of help you need. Include in the email the code of your approach.
From this block of exercises, in class we solve together the ones marked with 🔶. They are the same as the one listed below.2
Minimum Spanning Trees
Given a weighted undirected connected graph G = (V,E), with weights w\colon E \to\mathbb R, a minimum spanning tree (MST) of G is a spanning tree T and its total weight w(T) = \sum_{e\in E(T)} w(e) is minimum among all possible spanning trees of G.
Jarník-Prim algorithm is a greedy algorithm that given an input a weighted undirected connected graph finds a minimum spanning tree in it. In this algorithm we maintain a subset of visited vertices; each iteration of the algorithm selects the edge of minimum weight that joins a visited vertex u with a non-visited vertex v. Such edge cannot create a cycle, and it is added to the current set of edges for the spanning tree; furthermore vertex v is marked as visited. The set of edges so far is a spanning tree of the visited vertices. Since at each step we select the edge of minimum weight crossing the visited and non-visited vertices at the end the resulting set of edges will be a tree of minimum weight.
See an animation of a run of Jarník-Prim’s algorithm!
A possible C++ implementation of Jarník-Prim algorithm is the following:
#include<vector>
#include<queue>
using edge = pair<int,int>;
using wedge = pair<double,edge>;//weighted edge
using graph = vector<list<weighted_edge>>;
void Prim(const graph& G, list<edge>& MST, double& MST_weight) {
int n = G.size();
<bool> visited(n, false);
vector<wedge,vector<wedge>,greater<wedge>> Candidates;
priority_queue
[0] = true;
visited= 0.0;
MST_weight
for (wedge e : G[0]) Candidates.push(e);
while (MST.size() < n-1) {
int weight = Candidates.top().first;
= Candidates.top().second;
edge uv .pop();
Candidatesint v = uv.second;
if (not visited[v]) {
[v] = true;
visited.push_back(uv);
MST+= weight;
MST_weight for (wedge e : G[e]) Candidates.push(e);
}
}
}
Jutge exercises
The Jutge exercises that can be thought as variations on Jarník-Prim’s algorithm are the following:
Remember that for any doubt on the exercises you can ask me by email too until 6 days before the lab exam.