In competitive programming, graph theory and its algorithms are some of the most important themes that cannot be avoided. Many of the problems presented in contests like AtCoder, Codeforces, and TopCoder have a graph structure behind them. They serve as a powerful weapon for abstracting and solving real-world problems, such as finding the shortest path in a road network, minimizing communication costs in a network, and resolving task dependencies.
In this article, we will completely cover the major graph algorithms that frequently appear in competitive programming (Topological Sort, Dijkstra’s Algorithm, Bellman-Ford Algorithm, Floyd-Warshall Algorithm, Kruskal’s Algorithm, Prim’s Algorithm, and Strongly Connected Component Decomposition), including their theoretical backgrounds, computational complexity evaluations using mathematical formulas, and highly optimized implementation examples in modern C++ (C++17/20). Delivered in a massive volume of about 10,000 characters, this is truly a “complete guide”.
1. Basics and Constraints of Graph Algorithms
Before learning the algorithms, it is important to grasp the general constraints and computational complexity guidelines for graph problems in competitive programming. A graph is represented by the number of vertices $V$ (Vertices) and the number of edges $E$ (Edges).
- $O(V + E)$ : The computational complexity required for problems with a number of vertices $V, E \le 10^5 \sim 10^6$. Depth-First Search (DFS) and Breadth-First Search (BFS) fall into this category.
- $O((V + E) \log V)$ : Frequently appears in problems with $V, E \le 10^5 \sim 2 \cdot 10^5$. This is the computational complexity when using a priority queue in Dijkstra’s algorithm or Prim’s algorithm.
- $O(V^2)$ : Allowed for dense graphs ($E \approx V^2$) where $V \le 2000 \sim 3000$.
- $O(V^3)$ : Problems where $V \le 400 \sim 500$. The Floyd-Warshall algorithm is a typical example.
In competitive programming, it is common to use an Adjacency List to represent a graph. Since an adjacency matrix consumes $O(V^2)$ memory, it will hit the Memory Limit Exceeded error in problems with a large number of vertices.
2. Graph Traversal and Ordering
Topological Sort
Topological sort is an algorithm that arranges the vertices of a Directed Acyclic Graph (DAG) in a line such that all directed edges point from earlier vertices to later vertices. It is used when resolving task dependencies (e.g., Task B cannot start until Task A is finished) and for determining the calculation order of Dynamic Programming (DP) on a DAG.
The computational complexity is $O(V + E)$. There are two types of implementations: Kahn’s algorithm (BFS-based using in-degrees) and DFS-based using post-order traversal. Here, we introduce Kahn’s algorithm, which can easily find the lexicographically smallest topological sort.
C++ Implementation Example (Kahn’s Algorithm)
| |
3. Single Source Shortest Path (SSSP)
This is the problem of finding the shortest paths from a given source vertex to all other vertices. The applicable algorithms differ depending on whether the edge weights are non-negative or if negative weights exist.
Dijkstra’s Algorithm
Dijkstra’s algorithm is a fast shortest path algorithm that can be applied when all edge weights are non-negative. It is based on a greedy approach: “finalize the vertex with the shortest currently known distance, and update (relax) the distances to its adjacent vertices from that vertex.”
Mathematical Formula for Relaxation
Let the source be $s$, the shortest distance to vertex $u$ be $d[u]$, and the weight of edge $(u, v)$ be $w(u, v)$. The update equation is as follows:
$$ d[v] = \min(d[v], d[u] + w(u, v)) $$By using a priority queue (std::priority_queue), the unfinalized vertex with the minimum distance can be extracted in $O(\log V)$, making the overall time complexity $O((V + E) \log V)$. The space complexity is $O(V + E)$.
As shown in the figure above, the cost to go directly from S to B is 5, but going via A allows reaching it with a cost of 3. Dijkstra’s algorithm performs optimizations in this way.
C++ Implementation Example
| |
The statement if (dist[u] < d) continue; is very important. In Dijkstra’s algorithm, the same vertex might be pushed to the queue multiple times, but this check prunes unnecessary explorations.
Bellman-Ford Algorithm
When edge weights include negative values, Dijkstra’s algorithm cannot deduce the correct answer. This is where the Bellman-Ford algorithm comes into play. By repeating the relaxation process for all edges $V - 1$ times, it correctly calculates the shortest path even if there are negative weights.
If an update occurs even on the $V$-th iteration, it means a Negative Cycle exists. In competitive programming, problems asking to “detect a negative cycle” are frequent, and the Bellman-Ford algorithm is also excellent as a detection algorithm for this.
Note that the time complexity is $O(V \times E)$, which is slower than Dijkstra’s algorithm, so it can only be applied under constraints of about $V \le 2000, E \le 5000$.
C++ Implementation Example
| |
4. All-Pairs Shortest Path (APSP)
Floyd-Warshall Algorithm
This is an algorithm to find the shortest distances between all pairs of vertices in a graph. It is based on Dynamic Programming (DP). It is attractive because the algorithm is very concise and extremely easy to implement.
The state transition equation is as follows. We adopt the shorter of the path going through vertex $k$ and the path not going through it.
$$ d[i][j] = \min(d[i][j], d[i][k] + d[k][j]) $$Since it uses a triple loop, the time complexity is $O(V^3)$ and the space complexity is $O(V^2)$. If the number of vertices is around $V \le 400$, it will be in time for the execution time limit (usually 2 seconds).
C++ Implementation Example
| |
The Floyd-Warshall algorithm can also detect negative cycles. After the loop ends, if there is even one vertex i where dist[i][i] < 0, the graph contains a negative cycle.
5. Minimum Spanning Tree (MST)
In a connected undirected graph, a tree that connects all vertices (a subgraph without cycles) and minimizes the total sum of edge weights is called a Minimum Spanning Tree (MST). It is directly asked in scenarios like minimizing network construction costs.
Kruskal’s Algorithm
This is a greedy algorithm that sorts all edges in ascending order of their weights and successively adopts them, making sure not to form cycles. By using a Union-Find (Disjoint Set) data structure to check for cycles, it can be processed quickly.
The time complexity is $O(E \log E)$, as sorting the edges becomes the bottleneck. This is the most frequently used MST construction algorithm in competitive programming.
C++ Implementation Example
| |
Prim’s Algorithm
It takes a very similar approach to Dijkstra’s algorithm. Starting from one vertex, it successively selects the edge with the smallest weight among those directly connected to the already formed tree, growing the tree.
The computational complexity when using a priority queue is $O((V + E) \log V)$. For dense graphs (graphs with a large number of edges), an array-based implementation of Prim’s algorithm in $O(V^2)$ can be faster than Kruskal’s algorithm.
C++ Implementation Example
| |
6. Advanced: Strongly Connected Components (SCC)
In a directed graph, a set of vertices that can mutually reach each other is called a Strongly Connected Component (SCC). If any directed graph is grouped by its strongly connected components, the entire graph will always become a DAG (Directed Acyclic Graph). This is called Strongly Connected Component Decomposition. It is a very important preprocessing step to simplify graph structures and make problems easier to solve.
In competitive programming, it is heavily used in scenarios like solving 2-SAT problems or reducing a graph with cycles into a DAG to perform DP.
Kosaraju’s Algorithm
Kosaraju’s algorithm is an elegant and efficient method that can construct an SCC with just two passes of DFS (Depth-First Search). The computational complexity operates in linear time, $O(V + E)$.
Algorithm steps:
- Perform DFS on the original graph and record the vertices in an array in post-order.
- Create a reversed graph where the directions of all edges are inverted.
- Perform DFS on the unvisited vertices in the reversed graph, proceeding in the reverse order of the array recorded in step 1 (from the latest post-order to the earliest). The set of vertices reachable in a single DFS pass forms one SCC.
C++ Implementation Example
| |
The comp array stores the ID of the SCC each vertex belongs to. This ID actually has the very convenient property of being assigned in topological sort order. In other words, by looking at the values of comp, you can immediately understand the dependencies after reducing the graph to a DAG.
7. Conclusion and Study Advice
In this article, we comprehensively reviewed the graph algorithms that frequently appear in competitive programming. The key to improving in graph problems is “implementing them repeatedly until they become muscle memory” and “training yourself to think about what kind of graph a problem can be reduced to (what are the vertices, and what are the edges).”
- First, make sure you can quickly write DFS / BFS without making mistakes.
- Next, be able to write Dijkstra’s algorithm and Kruskal’s algorithm from memory (essential for AtCoder Brown to Green tiers).
- Finally, expand your repertoire with Bellman-Ford, Floyd-Warshall, Topological Sort, SCC, etc. (these become powerful weapons in AtCoder Cyan to Blue tiers).
We highly recommend modularizing them as code snippets (saving them in a snippet tool or your own GitHub repository) so that you can call them without hesitation during a real contest.
Graph algorithms in competitive programming are a field where you can most feel the beauty and power of algorithms. Please try copying the code in this article by hand and tackling past problems on online judges!
