Ch4
Shortest Path🔗
Definition: A shortest path between two vertices s and t in a network(weighted digraphs) is a directed simple path from s to t with property that no other such path has a lower weight.
Source-sink shortest path Given a start vertex s and a finish vertex t, find a shortest path in graph from s to t. s-source vertex and t-sink vertex.
Single-source shortest path Given a start vertex s, find the shortest path from s to each other vertex in graph.
All-pairs shortest path Find the shortest path connecting each pair of vertices in the graph, we sometimes use the term *all shortest * to refer to this set of paths.
Underlying Principle🔗
We apply repeatedly two operations
Edge relaxation Test whether travelling along a given edges gives a new shortest path to its destination vertex.
Path relaxation Test whether traveling through a given vertex gives a new shortest path connecting two other given vertices.
if(wt [w] > wt[v] + e->wt()) {
wt[w] = wt[v] + e->wt(); // edge-relaxation
spt[w] = e; // path-relaxation
}
Property: If a vertex x is on a shortest path from s to t, then that path consists of a shortest path from s to x followed by a shortest path from x to t.
Dijkstra’s Algorithm (SPSP)🔗
Property 2: Dijkstra’s algorithm solves the single-source shortest-paths problem in networks that have nonnegative weights.
Dijkstra’s original implementation, which is suitable for dense graphs, is precisely like Prim’s MST algorithm. Specifically we simple change the assignment of priority P from p=e->wt()
(edge wt) to p=wt[v]+e->wt()
( distance from the source to edge’s destination)
Property 3 With Dijkstra’s algorithm, we can find any SPT in a dense network in linear time.
Property 4 For all networks and all priority functions, we can compute a spanning tree with PFS in time proportional to the time required for V insert , V delete the minimum , and E decrease key operations in a priority queue of size at most V.
Property 5 With a PFS implementation of Dijkstra’s algorithm that uses a heap for the priority-queue implementation, we can compute any SPT in time proportional to E lg V.
int dijkstra(int V, vector<vector<int>>& edges, int src) {
// Construct the graph as an adjacency list
vector<vector<pair<int, int>>> graph(V + 1); // {weight, destination}
vector<bool> vis(V + 1, false);
vector<int> dis(V + 1, INT_MAX);
//$ vector<int> p(int,-1);
int processed = 0;
for (const auto& e : edges) {
graph[e[0]].push_back({e[2], e[1]}); // {weight, destination}
}
// Min-heap: stores {distance, vertex}
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq;
// Initialize source node
pq.push({0, src});
dis[src] = 0;
while (!pq.empty()) {
auto [d, u] = pq.top();
pq.pop();
if (vis[u])
continue;
vis[u] = true;
processed++;
// Relax neighbors
for (const auto& [w, v] : graph[u]) {
if (!vis[v] && d + w < dis[v]) {
dis[v] = d + w;
//$ p[v] = u;
pq.push({dis[v], v});
}
}
}
// Return the maximum shortest distance or -1 if not all vertices are reachable
return processed == V ? *max_element(dis.begin() + 1, dis.end()) : -1;
}
A variant of above algorithm can be used to print the path as well. Uncomment lines //$
vector<int> restore_path(int s, int d, vector<int>& p) {
vector<int> path;
for(int v = d; v!=s; v=p[v])
path.push_back(v);
path.push_back(s);
// reverse the path do correct the ordering
reverse(path.begin(),path.end());
return path;
}
Floyd-Warshall Algorithm (APSP)🔗
void floydWarshall(int V, vector<vector<int>>& edges) {
// Initialize distance matrix
vector<vector<int>> dis(V, vector<int>(V, INT_MAX));
// Set diagonal to 0 (distance to itself)
for (int i = 0; i < V; i++)
dis[i][i] = 0;
// Populate initial distances from edges
for (const auto& e : edges)
dis[e[0]][e[1]] = e[2]; // edge from u to v with weight w
// Floyd-Warshall algorithm
for (int k = 0; k < V; k++) { // Intermediate vertex
for (int u = 0; u < V; u++) { // Source vertex
for (int v = 0; v < V; v++) { // Destination vertex
if (dis[u][k] != INT_MAX && dis[k][v] != INT_MAX)
dis[u][v] = min(dis[u][v], dis[u][k] + dis[k][v]);
}
}
}
}
Bellman Ford Algorithm🔗
- Used to find the shortest path from a source node to all other nodes in a graph, even if there are negative edge weights.
- Key Features:
- Works for both directed and undirected graphs.
- Handles negative edge weights, unlike Dijkstra’s algorithm.
- Can also detect negative weight cycles in the graph.
- Time Complexity: , where V is the number of vertices and E is the number of edges.
vector<int> bellmanFord(int V, int E, vector<Edge>& edges, int source) {
// Initialize distances from the source
vector<int> dist(V, INT_MAX);
dist[source] = 0;
// Relax edges V-1 times
for (int i = 1; i < V; ++i) {
for (const auto& edge : edges) {
int u = edge.u;
int v = edge.v;
int w = edge.weight;
// If the distance to the destination vertex is greater
// through the current edge, update the distance
if (dist[u] != INT_MAX && dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
}
}
}
// Check for negative weight cycles
for (const auto& edge : edges) {
int u = edge.u;
int v = edge.v;
int w = edge.weight;
// If the distance can still be minimized, it means there's a negative cycle
if (dist[u] != INT_MAX && dist[u] + w < dist[v]) {
cout << "Graph contains negative weight cycle!" << endl;
return {}; // Return an empty vector to indicate a negative cycle
}
}
return dist; // Return the shortest distances from the source
}
Problems🔗
- https://www.geeksforgeeks.org/problems/shortest-path-in-undirected-graph-having-unit-distance/1 NOTICE few things, first there is no need to track visited since in undirected grpah it processes layer by layer, its a special case of floyd warshal which is special case PFS.
- https://www.geeksforgeeks.org/problems/shortest-path-in-undirected-graph/1 NOTICE NOW we need visited array to for safeguard and efficiency. Try removing it and see memory comparisons.
- https://www.geeksforgeeks.org/problems/implementing-dijkstra-set-1-adjacency-matrix/1 for fun try to use set for this approach to implement dijkstras
Aspect | Priority Queue | Set |
---|---|---|
Insertion Time | ||
Update Time | Not supported directly; push new value instead | : Remove old value, insert new value |
Deletion Time | ||
Ease of Implementation | Simple (standard libraries like priority_queue ) |
More complex, as updates require additional operations |
Duplicates | Allows duplicates | Does not allow duplicates |
Space Usage | May grow larger due to duplicate entries | Minimal, as it keeps only one instance of each node |
Efficiency in Practice | Generally faster due to less overhead | Slightly slower for the same operations due to extra bookkeeping |
- https://leetcode.com/problems/shortest-path-in-binary-matrix/ Now it maybe tempting to store all distances but the quesiton only ask for a specific target, so try to optimize space without using matrix to store all distances.
-
https://leetcode.com/problems/number-of-ways-to-arrive-at-destination/ The best solution would be to keep track of number of ways in an array ways and increment each time your traverse priority queue.
-
https://leetcode.com/problems/path-with-minimum-effort/description/ This problem requires to drive dijkstra in the direction where the abs difference encountered so far is minimum, so our priority function changes here.
-
https://leetcode.com/problems/network-delay-time/description/ This is straightforward question find all distances and then find the longest distance from the dist array.
-
https://www.geeksforgeeks.org/problems/minimum-multiplications-to-reach-end/1 This problem does have any graph structure and we don’t actually need to create the graph, its an application of PFS (doesn’t really matter as PFS function is not obvious). Think of it as a graph with 1e5 nodes and you are traversing using multiplication rules.
-
https://leetcode.com/problems/cheapest-flights-within-k-stops/ Cheapest flight with K stops, this is important question look at DFS solution presented as well.
int dfs(vector<vector<pair<int,int>>> &graph, vector<vector<int>> &vis, int i, int dst, int k) {
if(i == dst){
return 0;
}
if(k < 0) {
return INT_MAX;
}
if(vis[i][k] != 0) {
return vis[i][k];
}
int cost = INT_MAX;
for(auto nbr: graph[i]) {
int next = nbr.first;
int weight = nbr.second;
int subproblem = dfs(graph, vis, next, dst, k - 1);
if (subproblem != INT_MAX) {
cost = min(cost, weight + subproblem);
}
}
return vis[i][k] = cost;
}
int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int k) {
// looks like a weighted graph with bfs
// vector<vector<pair<int,int>>> graph(n);
unordered_map<int, vector<pair<int,int>>> graph;
// construct graph
for(auto f : flights) {
graph[f[0]].push_back({f[1],f[2]});
}
// ----------- bfs ----------
queue<pair<int,int>> q;
vector<int> dist(n+1, INT_MAX);
dist[src] = 0;
q.push({src,0});
while(!q.empty() && k >= 0) {
// travel all current level nodes - level order traversal
int n = q.size();
for(int i = 0; i < n; i++) {
auto t = q.front(); q.pop();
// do edge relaxation from all neighbors
for(auto nbr: graph[t.first]) {
if(t.second + nbr.second < dist[nbr.first]) {
dist[nbr.first] = t.second + nbr.second;
q.push({nbr.first, dist[nbr.first]});
}
}
}
k--;
}
return dist[dst] >= INT_MAX ? -1 : dist[dst];
// ----------- dfs -----------
// vector<vector<int>> vis(n, vector<int>(k + 2, 0)); // Initializing vis with -1
// start from src and do bfs till k dist or destination
// int result = dfs(graph, vis, src, dst, k);
// return result == INT_MAX ? -1 : result;
}
- https://www.geeksforgeeks.org/problems/distance-from-the-source-bellman-ford-algorithm/1 Use bellman ford
- https://www.geeksforgeeks.org/problems/distance-from-the-source-bellman-ford-algorithm/1 Use floyd warshal
- https://leetcode.com/problems/find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance/description/ Use floyd warshall and do post processing on resulting matrix as per conditions presented.