Exploring Time Complexity of Topological Sort Algorithm in Python
This article is a complementary resource to the DSA with Python course.
This article is a complementary resource to the DSA with Python course.
Topological sorting is an algorithm in graph theory that is used to order the vertices of a directed acyclic graph (DAG) such that for every edge (u, v)
, vertex u
comes before vertex v
in the ordering.
In this article, we'll delve into the time complexity of the topological sort algorithm.
Topological sort works by
def topological_sort(graph, sorted_vertices=None):
if sorted_vertices is None:
sorted_vertices = []
# Base case
if len(sorted_vertices) == len(graph):
return [vertices[i] for i in sorted_vertices]
# Find indegree of all vertices
indegrees = [sum(col) for col in zip(*graph)]
# Find the vertices with indegree zero
zero_indegrees = [i for i, indegree in enumerate(indegrees) if indegree == 0]
# Append the zero indegree list to sorted
sorted_vertices.extend([i for i in zero_indegrees if i not in sorted_vertices])
# Create a copy of the graph to avoid modifying the input
new_graph = [row[:] for row in graph]
# Remove the zero indegree vertices
for i in zero_indegrees:
new_graph[i] = [0] * len(new_graph[i])
# Sort the new graph
return topological_sort(new_graph, sorted_vertices)
The above program implements a topological sort algorithm using a recursive approach.
In the worst-case scenario, the program makes V
recursive calls, where V
is the number of vertices.
In each recursive call, the program performs the following operations:
1. Finding the Indegree of all Vertices
This operation involves transposing the adjacency matrix and then summing the columns to find the indegree of each vertex.
The transposition itself takes <katex>O(V^{2})</katex>
time, and the subsequent summing of columns also takes <katex>O(V^{2})</katex>
time, resulting in a total time complexity of <katex>O(V^{2})</katex>
.
2. Finding the Vertices with Indegree Zero
This operation involves iterating over the list of indegrees and checking if each indegree is zero. Since there are V
indegrees, this operation takes O(V)
time.
3. Removing the Zero Indegree Vertices
This operation involves iterating over the list of zero-indegree vertices and setting the corresponding rows in the adjacency matrix to zero.
Since there are at most V
zero-indegree vertices, and each row has V
elements, this operation takes <katex>O(V^{2})</katex>
time.
But the program makes V
recursive calls. So, the total time complexity of the recursive call is
However, this is not the optimal solution since it computes the indegrees of all vertices at each recursive step. This is unnecessary because the indegrees of the vertices that have not been removed do not change.
Also, it uses recursion to sort the graph.
A more efficient approach would be to use a loop to iteratively remove vertices with zero indegree.
from collections import defaultdict, deque
def topological_sort(vertices, adj_matrix):
# Create an adjacency list representation of the graph
graph = defaultdict(list)
indegrees = {vertex: 0 for vertex in vertices}
for i, row in enumerate(adj_matrix):
for j, val in enumerate(row):
if val == 1:
graph[vertices[j]].append(vertices[i])
indegrees[vertices[i]] += 1
# Initialize a queue with vertices having indegree 0
queue = deque([vertex for vertex, indegree in indegrees.items() if indegree == 0])
# Initialize an empty list to store the sorted vertices
sorted_vertices = []
# Perform the topological sort
while queue:
vertex = queue.popleft()
sorted_vertices.append(vertex)
for neighbor in graph[vertex]:
indegrees[neighbor] -= 1
if indegrees[neighbor] == 0:
queue.append(neighbor)
# Check for cycles
if len(sorted_vertices) != len(vertices):
raise ValueError("Graph contains a cycle")
return sorted_vertices
The time complexity of the above program can be broken down into four main steps:
1. Creating the Adjacency List Representation
This step involves iterating over the adjacency matrix, which has a time complexity proportional to the number of edges E
.
So, it has a time complexity of O(E)
.
2. Initializing the Queue with Vertices Having Indegree 0
This step involves iterating over the vertices, which takes time proportional to the number of vertices V
.
So, it has a time complexity of O(V)
.
3. Performing the Topological Sort
This step involves iterating over the vertices and their neighbors, resulting in a time complexity proportional to the number of vertices V
and edges E
.
So, it has a time complexity of O(V + E)
.
4. Checking for Cycles
This step involves iterating over the vertices, which takes time proportional to the number of vertices V
. So, it has a time complexity of O(V)
.
Since each step's time complexity is proportional to V
or E
, the overall time complexity is proportional to V + E
.
So, it has a time complexity of O(V + E)
because the algorithm only visits each vertex and edge once.