Dijkstra Algorithm:
Dijkstra’s Algorithm is a classic algorithm in Computer Science used to find the shortest path from a single source node. This graph can represent, for example, road networks, where intersections are nodes and roads are edges with eights (distances or costs. The algorithm assumes that all edge weights are non-negative.
The main idea is to repeatedly select the node with the smallest tentative distance, then update its neighbours. This approach guarantees that we are always processing nodes in the order of their shortest distance from the starting node.
Basic Steps:
Template:
public Dictionary<int,int> FindShortestDistance(int source, Dictionary<int, List<Tuple<int,int>>> graph)
{
var distance = new Dictionary<int, int>();
var previousNode = new Dictionary<int, int?>();
var queue = new PriorityQueue<int, int>();
foreach(var node in graph.Keys)
{
distance[node] = int.MaxValue;
previousNode[node] = null;
}
distance[source] = 0;
queue.Enqueue(source, 0);
while(queue.Count > 0)
{
queue.TryDequeue(out int currentNode, out int currentDistance);
foreach(var neightbour in graph[currentNode])
{
var neighbourNode = neightbour.Item1;
var newDistance = currentDistance + neightbour.Item2;
if(newDistance > distance[neighbourNode])
{
distance[neighbourNode] = newDistance;
previousNode[neighbourNode] = currentNode;
queue.EnqueueDequeue(neighbourNode, currentDistance);
}
}
}
return distance;
}
Bellman-Ford:
The Bellman-Ford algorithm is used to find shortest path from a single node to all other nodes in a graph. Unlike Dijkstra’s algorithm, the Bellman-Ford can handle graphs with negative edge weights. However it cannot work with graphs that contain negative weight cycles (cycles where the sum of edge weights is negative.
The algorithm works by relaxing the edges repeatedly, relaxing an edge means updating the shortest distance to the target node of the edge, if a shorter path is found via the edge.
Step for Bellman-Ford:
Template:
public int[] Bellman(List<Edge> edges, int vertices, int source)
{
var distance = new int[vertices+1];
Array.Fill(distance, int.MaxValue);
distance[source] = 0;
//relaxing v-1 times
for(int i = 0; i < vertices; i++)
{
foreach(var edge in edges)
{
if (distance[edge.Source] != int.MaxValue && distance[source] + edge.Weight < distance[edge.Destination])
{
distance[edge.Destination] = distance[source] + edge.Weight;
}
}
}
// relaxing one more time to check for negative edge weight cycle
foreach (var edge in edges)
{
if (distance[edge.Source] != int.MaxValue && distance[source] + edge.Weight < distance[edge.Destination])
{
throw new InvalidOperationException("negative-weight cycle");
}
}
return distance;
}
Detailed explanation on edge relaxation:
Why Relax Edges V-1 Times?
Imagine a situation where you're trying to find the shortest path from a starting point (source node) to all other nodes in a graph. If all edges have positive weights, each time you "relax" an edge, you may update the shortest path to a node. However, if there are negative weights, the shortest path to a node might not be immediately obvious, and it could take a few iterations (or relaxations) to update the shortest path correctly.
Here's why we relax edges V−1 times:
1. Shortest Path Length:
A shortest path from the source to any node can have at most V-1 edges. This is because in a graph with VVV nodes, the longest possible path you can have between two nodes (without visiting the same node twice) has V−1 edges.
For example:
If you have 5 nodes (let’s say V=5), the longest possible path between the source and any other node can have at most 4 edges.
2. Relaxing Once Isn’t Enough:
3. Why V-1 Relaxations?:
4. Why not more than V-1 Relaxations?
Summary:
• Relaxing an edge means checking if the shortest distance to a destination node can be improved by going through a source node.
• We relax edges V-1 times because the longest shortest path from the source node to any other node can have at most V-1 edges. After V-1 relaxations, all the shortest paths should be correctly computed.
• If any edge can still be relaxed after V-1 iterations, it indicates the presence of a negative-weight cycle in the graph.
Floyd-Warshall:
The floydd warshall is a dynamic programming algorithm used to find the shortest path between all pairs of vertices in a weighted graph. It can handle both directed and undirected graphs.
It works if there are negative edge weights but no negative edge weight cycle.
How it works?
Imagine you have a graph where the nodes represents the places and edges represent the paths between those two places. The algorithm does this by considering every possible “intermediate” node and checks if a path through that node offers a shorter distance between two other nodes.
The algorithm maintains a matrix dist[][] where dist[i][j] represents the shortest path / distance from node I to node j. Initially the matrix is set to edge weights, and for nodes without a direct edge the matrix is set to infinity. The algorithm than iteratively updates the matrix considering each node as intermediate node.
Steps:
Template:
public int[,] RunFloyddWarshall(int[,] graph)
{
int n = graph.GetLength(0);
var dist = new int[n,n];
//Initialize the distance matrix with graph weights
for(int i = 0; i < n; i++)
{
for(int j = 0; j < n; j++)
{
if(i == j)
{
dist[i, j] = 0;
}
else if (graph[i,j] != 0)
{
dist[i, j] = graph[i,j];
}
else
{
dist[i, j] = int.MaxValue;
}
}
}
for(int k = 0; k < n; k++)
{
for(int i = 0; i < n; i++)
{
for(int j = 0; j < n; j++)
{
if (dist[i,k] != int.MaxValue && dist[k,j] != int.MaxValue &&
dist[i,k] + dist[k,j] < dist[i, j])
{
dist[i, j] = dist[i, k] + dist[k, j];
}
}
}
}
return dist;
}
Prim’s Algorithm:
It is a greedy algorithm that is used to find the minimum spanning tree of a weighted, undirected graph. The algorithm works by starting with an arbitrary vertex and growing the MST one edge at a time by always choosing the minimum weight edge that connects a vertex in the MST to a Vertex outside the MST.
The algorithm can be summarized as follows:
Key Concept:
MST: A subset of edges that connects all the vertices in the graph without any cycles and with the minimum possible total edge weight.
Greedy Choice Property: Prim’s algorithm is greedy because at each step it chooses the minimum weighted edge which connected the MST to the rest of the graph.
Template:
public int RunPrim(int vertices, List<Edge>[] graph)
{
// Exactly like Dijkstra
var queue = new PriorityQueue<int, int>();
var inMst = new bool[vertices];
queue.Enqueue(0, 0);
int totalWeight = 0;
while(queue.Count > 0)
{
queue.TryDequeue(out var node, out var weight);
if (inMst[node]) continue;
inMst[node] = true;
totalWeight += weight;
foreach(var neighbour in graph[node])
{
if (!inMst[neighbour.Destination])
{
queue.Enqueue(neighbour.Destination, neighbour.Weight);
}
}
}
return totalWeight;
}
Kruskals Algorithm:
Kruskal’s algorithm is a greedy algorithm used to find the MST of a connected, undirected graph. The minimum spanning tree is a subgraph or a subset of edges that connects all the vertices and don’t form any cycles with the minimum total edge weight possible.
Steps:
Key Concepts:
Template:
using System;
using System.Collections.Generic;
class Kruskal
{
// Union-Find (Disjoint Set) class
public class UnionFind
{
private int[] parent, rank;
public UnionFind(int size)
{
parent = new int[size];
rank = new int[size];
for (int i = 0; i < size; i++)
{
parent[i] = i;
rank[i] = 0;
}
}
public int Find(int u)
{
if (parent[u] != u)
parent[u] = Find(parent[u]); // Path compression
return parent[u];
}
public void Union(int u, int v)
{
int rootU = Find(u);
int rootV = Find(v);
if (rootU != rootV)
{
// Union by rank
if (rank[rootU] > rank[rootV])
parent[rootV] = rootU;
else if (rank[rootU] < rank[rootV])
parent[rootU] = rootV;
else
{
parent[rootV] = rootU;
rank[rootU]++;
}
}
}
}
// Edge class to represent an edge (u, v) with weight w
public class Edge
{
public int u, v, weight;
public Edge(int u, int v, int weight)
{
this.u = u;
this.v = v;
this.weight = weight;
}
}
// Function to perform Kruskal's Algorithm
public static List<Edge> KruskalMST(int vertices, List<Edge> edges)
{
// Sort edges by weight
edges.Sort((e1, e2) => e1.weight.CompareTo(e2.weight));
UnionFind uf = new UnionFind(vertices);
List<Edge> mst = new List<Edge>();
foreach (var edge in edges)
{
int u = edge.u;
int v = edge.v;
// If u and v are in different sets, include the edge in the MST
if (uf.Find(u) != uf.Find(v))
{
uf.Union(u, v);
mst.Add(edge);
}
}
return mst;
}
public static void Main()
{
// Example usage
int vertices = 4;
List<Edge> edges = new List<Edge>
{
new Edge(0, 1, 10),
new Edge(0, 2, 6),
new Edge(0, 3, 5),
new Edge(1, 3, 15),
new Edge(2, 3, 4)
};
List<Edge> mst = KruskalMST(vertices, edges);
Console.WriteLine("Edges in the Minimum Spanning Tree:");
foreach (var edge in mst)
{
Console.WriteLine($"{edge.u} - {edge.v} : {edge.weight}");
}
}
}