Advanced Graph Algorithms
Shortest Path Algorithms
Dijkstra’s Algorithm
Conceived by Edsger W. Dijkstra in 1956, published in 1959. Dijkstra reportedly designed it in 20 minutes while shopping with his fiancée.
When to use: Finding shortest path in weighted graphs with non-negative weights
Key properties:
- Greedy algorithm that always selects nearest unvisited vertex
- Guarantees shortest path when all edge weights are non-negative
- Time: O((V + E) log V) with priority queue, O(V²) with array
- Cannot handle negative edge weights (use Bellman-Ford instead)
public static class ShortestPath
{
public static Dictionary<int, int> Dijkstra(WeightedGraph graph, int startVertex, List<int> allVertices)
{
var distances = new Dictionary<int, int>();
var visited = new HashSet<int>();
var priorityQueue = new PriorityQueue<int, int>();
// Initialize distances
foreach (int vertex in allVertices)
{
distances[vertex] = int.MaxValue;
}
distances[startVertex] = 0;
priorityQueue.Enqueue(startVertex, 0);
while (priorityQueue.Count > 0)
{
int current = priorityQueue.Dequeue();
if (visited.Contains(current)) continue;
visited.Add(current);
foreach (var edge in graph.GetNeighbors(current))
{
int neighbor = edge.Destination;
int newDistance = distances[current] + edge.Weight;
if (newDistance < distances[neighbor])
{
distances[neighbor] = newDistance;
priorityQueue.Enqueue(neighbor, newDistance);
}
}
}
return distances;
}
// Get actual path, not just distances
public static List<int> DijkstraPath(WeightedGraph graph, int start, int end, List<int> allVertices)
{
var distances = new Dictionary<int, int>();
var previous = new Dictionary<int, int>();
var visited = new HashSet<int>();
var priorityQueue = new PriorityQueue<int, int>();
foreach (int vertex in allVertices)
{
distances[vertex] = int.MaxValue;
}
distances[start] = 0;
priorityQueue.Enqueue(start, 0);
while (priorityQueue.Count > 0)
{
int current = priorityQueue.Dequeue();
if (current == end) break;
if (visited.Contains(current)) continue;
visited.Add(current);
foreach (var edge in graph.GetNeighbors(current))
{
int neighbor = edge.Destination;
int newDistance = distances[current] + edge.Weight;
if (newDistance < distances[neighbor])
{
distances[neighbor] = newDistance;
previous[neighbor] = current;
priorityQueue.Enqueue(neighbor, newDistance);
}
}
}
// Reconstruct path
var path = new List<int>();
int currentNode = end;
while (previous.ContainsKey(currentNode))
{
path.Add(currentNode);
currentNode = previous[currentNode];
}
path.Add(start);
path.Reverse();
return path;
}
}
A* Search Algorithm
Developed by Peter Hart, Nils Nilsson, and Bertram Raphael at Stanford Research Institute in 1968
When to use: Pathfinding with heuristic knowledge (games, GPS navigation, robotics)
Key properties:
- Extension of Dijkstra that uses heuristic to guide search toward goal
- f(n) = g(n) + h(n), where g = actual cost from start, h = estimated cost to goal
- Optimal if heuristic is admissible (never overestimates) and consistent
- Much faster than Dijkstra when good heuristic available
- Common heuristics: Manhattan distance (grid), Euclidean distance (2D/3D space)
public static class AStar
{
public class Node
{
public int Vertex { get; set; }
public int GScore { get; set; } // Distance from start
public int FScore { get; set; } // GScore + heuristic
public Node Parent { get; set; }
public Node(int vertex)
{
Vertex = vertex;
GScore = int.MaxValue;
FScore = int.MaxValue;
}
}
public static List<int> AStarSearch(WeightedGraph graph, int start, int goal,
Func<int, int, int> heuristic, List<int> allVertices)
{
var openSet = new PriorityQueue<Node, int>();
var allNodes = new Dictionary<int, Node>();
var openSetHash = new HashSet<int>();
// Initialize nodes
foreach (int vertex in allVertices)
{
allNodes[vertex] = new Node(vertex);
}
allNodes[start].GScore = 0;
allNodes[start].FScore = heuristic(start, goal);
openSet.Enqueue(allNodes[start], allNodes[start].FScore);
openSetHash.Add(start);
while (openSet.Count > 0)
{
var current = openSet.Dequeue();
openSetHash.Remove(current.Vertex);
if (current.Vertex == goal)
{
return ReconstructPath(current);
}
foreach (var edge in graph.GetNeighbors(current.Vertex))
{
var neighbor = allNodes[edge.Destination];
int tentativeGScore = current.GScore + edge.Weight;
if (tentativeGScore < neighbor.GScore)
{
neighbor.Parent = current;
neighbor.GScore = tentativeGScore;
neighbor.FScore = neighbor.GScore + heuristic(neighbor.Vertex, goal);
if (!openSetHash.Contains(neighbor.Vertex))
{
openSet.Enqueue(neighbor, neighbor.FScore);
openSetHash.Add(neighbor.Vertex);
}
}
}
}
return new List<int>(); // No path found
}
private static List<int> ReconstructPath(Node node)
{
var path = new List<int>();
var current = node;
while (current != null)
{
path.Add(current.Vertex);
current = current.Parent;
}
path.Reverse();
return path;
}
// Example heuristic for grid-based pathfinding (Manhattan distance)
public static int ManhattanDistance(int vertex1, int vertex2, int gridWidth)
{
int x1 = vertex1 % gridWidth, y1 = vertex1 / gridWidth;
int x2 = vertex2 % gridWidth, y2 = vertex2 / gridWidth;
return Math.Abs(x1 - x2) + Math.Abs(y1 - y2);
}
}
Minimum Spanning Trees
Kruskal’s Algorithm
When to use: Finding minimum spanning tree for connecting all vertices with minimum total edge weight
public static class MinimumSpanningTree
{
public class Edge : IComparable<Edge>
{
public int Source { get; set; }
public int Destination { get; set; }
public int Weight { get; set; }
public Edge(int source, int destination, int weight)
{
Source = source;
Destination = destination;
Weight = weight;
}
public int CompareTo(Edge other)
{
return Weight.CompareTo(other.Weight);
}
}
public static List<Edge> KruskalMST(List<Edge> edges, int vertexCount)
{
var result = new List<Edge>();
var unionFind = new UnionFind(vertexCount);
// Sort edges by weight
edges.Sort();
foreach (var edge in edges)
{
if (unionFind.Find(edge.Source) != unionFind.Find(edge.Destination))
{
result.Add(edge);
unionFind.Union(edge.Source, edge.Destination);
// MST has exactly V-1 edges
if (result.Count == vertexCount - 1)
break;
}
}
return result;
}
}
// Union-Find data structure for Kruskal's algorithm
public class UnionFind
{
private int[] parent;
private int[] 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 x)
{
if (parent[x] != x)
{
parent[x] = Find(parent[x]); // Path compression
}
return parent[x];
}
public void Union(int x, int y)
{
int rootX = Find(x);
int rootY = Find(y);
if (rootX != rootY)
{
// Union by rank
if (rank[rootX] < rank[rootY])
{
parent[rootX] = rootY;
}
else if (rank[rootX] > rank[rootY])
{
parent[rootY] = rootX;
}
else
{
parent[rootY] = rootX;
rank[rootX]++;
}
}
}
}
Prim’s Algorithm
When to use: Alternative MST algorithm, especially when graph is dense
public static List<MinimumSpanningTree.Edge> PrimMST(WeightedGraph graph, List<int> allVertices)
{
if (allVertices.Count == 0) return new List<MinimumSpanningTree.Edge>();
var result = new List<MinimumSpanningTree.Edge>();
var visited = new HashSet<int>();
var priorityQueue = new PriorityQueue<MinimumSpanningTree.Edge, int>();
// Start from first vertex
int startVertex = allVertices[0];
visited.Add(startVertex);
// Add all edges from start vertex to priority queue
foreach (var edge in graph.GetNeighbors(startVertex))
{
priorityQueue.Enqueue(
new MinimumSpanningTree.Edge(startVertex, edge.Destination, edge.Weight),
edge.Weight
);
}
while (priorityQueue.Count > 0 && visited.Count < allVertices.Count)
{
var minEdge = priorityQueue.Dequeue();
if (visited.Contains(minEdge.Destination))
continue;
// Add edge to MST
result.Add(minEdge);
visited.Add(minEdge.Destination);
// Add all edges from new vertex
foreach (var edge in graph.GetNeighbors(minEdge.Destination))
{
if (!visited.Contains(edge.Destination))
{
priorityQueue.Enqueue(
new MinimumSpanningTree.Edge(minEdge.Destination, edge.Destination, edge.Weight),
edge.Weight
);
}
}
}
return result;
}
Advanced Graph Problems
Strongly Connected Components (Kosaraju’s Algorithm)
When to use: Finding strongly connected components in directed graphs
public static class StronglyConnectedComponents
{
public static List<List<int>> FindSCC(Graph directedGraph, List<int> allVertices)
{
var visited = new HashSet<int>();
var finishStack = new Stack<int>();
// Step 1: Fill vertices in stack according to their finishing times
foreach (int vertex in allVertices)
{
if (!visited.Contains(vertex))
{
DFSFillOrder(directedGraph, vertex, visited, finishStack);
}
}
// Step 2: Create transpose graph
var transposeGraph = GetTranspose(directedGraph, allVertices);
// Step 3: Do DFS according to order defined in stack
visited.Clear();
var sccs = new List<List<int>>();
while (finishStack.Count > 0)
{
int vertex = finishStack.Pop();
if (!visited.Contains(vertex))
{
var scc = new List<int>();
DFSUtil(transposeGraph, vertex, visited, scc);
sccs.Add(scc);
}
}
return sccs;
}
private static void DFSFillOrder(Graph graph, int vertex, HashSet<int> visited, Stack<int> stack)
{
visited.Add(vertex);
foreach (int neighbor in graph.GetNeighbors(vertex))
{
if (!visited.Contains(neighbor))
{
DFSFillOrder(graph, neighbor, visited, stack);
}
}
stack.Push(vertex);
}
private static Graph GetTranspose(Graph graph, List<int> allVertices)
{
var transpose = new Graph();
foreach (int vertex in allVertices)
{
transpose.AddVertex(vertex);
}
foreach (int vertex in allVertices)
{
foreach (int neighbor in graph.GetNeighbors(vertex))
{
transpose.AddEdge(neighbor, vertex, false); // Reverse the edge
}
}
return transpose;
}
private static void DFSUtil(Graph graph, int vertex, HashSet<int> visited, List<int> component)
{
visited.Add(vertex);
component.Add(vertex);
foreach (int neighbor in graph.GetNeighbors(vertex))
{
if (!visited.Contains(neighbor))
{
DFSUtil(graph, neighbor, visited, component);
}
}
}
}
Network Flow (Ford-Fulkerson Algorithm)
When to use: Maximum flow problems, bipartite matching, capacity constraints
public static class NetworkFlow
{
public static int MaxFlow(int[,] capacity, int source, int sink)
{
int vertices = capacity.GetLength(0);
int[,] residualGraph = new int[vertices, vertices];
// Create residual graph
for (int i = 0; i < vertices; i++)
{
for (int j = 0; j < vertices; j++)
{
residualGraph[i, j] = capacity[i, j];
}
}
int[] parent = new int[vertices];
int maxFlowValue = 0;
// While there exists an augmenting path
while (BFS(residualGraph, source, sink, parent))
{
// Find minimum residual capacity along the path
int pathFlow = int.MaxValue;
for (int v = sink; v != source; v = parent[v])
{
int u = parent[v];
pathFlow = Math.Min(pathFlow, residualGraph[u, v]);
}
// Add path flow to overall flow
maxFlowValue += pathFlow;
// Update residual capacities
for (int v = sink; v != source; v = parent[v])
{
int u = parent[v];
residualGraph[u, v] -= pathFlow;
residualGraph[v, u] += pathFlow;
}
}
return maxFlowValue;
}
private static bool BFS(int[,] residualGraph, int source, int sink, int[] parent)
{
int vertices = residualGraph.GetLength(0);
bool[] visited = new bool[vertices];
var queue = new Queue<int>();
queue.Enqueue(source);
visited[source] = true;
parent[source] = -1;
while (queue.Count > 0)
{
int u = queue.Dequeue();
for (int v = 0; v < vertices; v++)
{
if (!visited[v] && residualGraph[u, v] > 0)
{
if (v == sink)
{
parent[v] = u;
return true;
}
queue.Enqueue(v);
visited[v] = true;
parent[v] = u;
}
}
}
return false;
}
}
Graph Coloring
Basic Graph Coloring (Greedy Approach)
When to use: Scheduling problems, register allocation, map coloring
public static class GraphColoring
{
public static Dictionary<int, int> GreedyColoring(Graph graph, List<int> allVertices)
{
var coloring = new Dictionary<int, int>();
var availableColors = new bool[allVertices.Count];
// Assign first color to first vertex
if (allVertices.Count > 0)
{
coloring[allVertices[0]] = 0;
}
// Assign colors to remaining vertices
for (int i = 1; i < allVertices.Count; i++)
{
int vertex = allVertices[i];
// Mark colors of adjacent vertices as unavailable
Array.Fill(availableColors, true);
foreach (int neighbor in graph.GetNeighbors(vertex))
{
if (coloring.ContainsKey(neighbor))
{
int neighborColor = coloring[neighbor];
if (neighborColor < availableColors.Length)
{
availableColors[neighborColor] = false;
}
}
}
// Find first available color
for (int color = 0; color < availableColors.Length; color++)
{
if (availableColors[color])
{
coloring[vertex] = color;
break;
}
}
}
return coloring;
}
public static bool IsValidColoring(Graph graph, Dictionary<int, int> coloring)
{
foreach (var vertex in coloring.Keys)
{
foreach (int neighbor in graph.GetNeighbors(vertex))
{
if (coloring.ContainsKey(neighbor) && coloring[vertex] == coloring[neighbor])
{
return false;
}
}
}
return true;
}
}
Time Complexity Summary
Algorithm | Time Complexity | Space Complexity | Use Case |
---|---|---|---|
Dijkstra | O((V + E) log V) | O(V) | Shortest path, non-negative weights |
A* | O((V + E) log V) | O(V) | Pathfinding with heuristic |
Kruskal’s MST | O(E log E) | O(V) | Minimum spanning tree |
Prim’s MST | O((V + E) log V) | O(V) | Minimum spanning tree (dense graphs) |
Kosaraju SCC | O(V + E) | O(V) | Strongly connected components |
Ford-Fulkerson | O(E × max_flow) | O(V²) | Maximum flow |
Graph Coloring | O(V²) | O(V) | Vertex coloring |
Real-World Applications
GPS Navigation Systems
- Dijkstra/A*: Find shortest/fastest route
- Dynamic programming: Handle real-time traffic updates
- Preprocessing: Precompute common routes for faster queries
Social Network Analysis
- BFS: Find degrees of separation
- Connected components: Identify communities
- PageRank algorithm: Rank user influence
Network Infrastructure
- MST algorithms: Design efficient network topology
- Maximum flow: Optimize data transmission capacity
- Graph coloring: Assign frequencies to avoid interference
Compiler Design
- Topological sort: Resolve dependencies
- Graph coloring: Register allocation
- DFS: Detect circular dependencies
Quick Reference
Advanced Algorithm Comparison
| Algorithm | Time | Space | Use Case | |———–|——|——-|———-| | Dijkstra | O((V+E) log V) | O(V) | Shortest path, non-negative weights | | Bellman-Ford | O(VE) | O(V) | Shortest path, negative edges allowed | | Floyd-Warshall | O(V³) | O(V²) | All-pairs shortest paths | | Kruskal’s MST | O(E log E) | O(V) | Minimum spanning tree | | Prim’s MST | O((V+E) log V) | O(V) | Minimum spanning tree (dense) | | Ford-Fulkerson | O(E × max_flow) | O(V) | Maximum flow |
Algorithm Selection Guide
- Single-source shortest path, no negative edges: Dijkstra
- Single-source, negative edges: Bellman-Ford
- All-pairs shortest path: Floyd-Warshall
- Minimum spanning tree (sparse): Kruskal’s
- Minimum spanning tree (dense): Prim’s
- Maximum flow: Ford-Fulkerson or Edmonds-Karp
Found this guide helpful? Share it with your team:
Share on LinkedIn