Topological Sort - kjingers/Grokking-Notes GitHub Wiki
This is how we can implement this algorithm:
a. Initialization
-
We will store the graph in Adjacency Lists, which means each parent vertex will have a list containing all of its children. We will do this using a HashMap where the βkeyβ will be the parent vertex number and the value will be a List containing children vertices.
-
To find the sources, we will keep a HashMap to count the in-degrees i.e., count of incoming edges of each vertex. Any vertex with β0β in-degree will be a source.
b. Build the graph and find in-degrees of all vertices
- We will build the graph from the input and populate the in-degrees HashMap.
c. Find all sources
- All vertices with β0β in-degrees will be our sources and we will store them in a Queue.
d. Sort
- For each source, do the following things:
- Add it to the sorted list.
- Get all of its children from the graph.
- Decrement the in-degree of each child by 1.
- If a childβs in-degree becomes β0β, add it to the sources Queue.
- Repeat step 1, until the source Queue is empty.
Topological Sort
from collections import deque
def topological_sort(vertices, edges):
sortedOrder = []
sources = deque()
graph = {}
inDegrees = {}
# If the number of vertices is 0, return empty list
if vertices <= 0:
return sortedOrder
# Initialize Adjacency List and Indegrees Hashmap
graph = {i: [] for i in range(vertices)}
inDegrees = {i: 0 for i in range(vertices)}
# Build the graph and Indegrees Hashmap
for parent, child in edges:
graph[parent].append(child)
inDegrees[child] += 1
# Add sources to source queue
for vertex in inDegrees:
if inDegrees[vertex] == 0:
sources.append(vertex)
# While source queue, pop source.
# - Add to sourtedOrder list
# - Decrement indegrees map
# - If 0 indegrees, add to sources queue
while sources:
source = sources.popleft()
sortedOrder.append(source)
for child in graph[source]:
inDegrees[child] -= 1
if inDegrees[child] == 0:
sources.append(child)
return sortedOrder
def main():
print("Topological sort: " +
str(topological_sort(4, [3, 2], [3, 0], [2, 0], [2, 1](/kjingers/Grokking-Notes/wiki/3,-2],-[3,-0],-[2,-0],-[2,-1))))
print("Topological sort: " +
str(topological_sort(5, [4, 2], [4, 3], [2, 0], [2, 1], [3, 1](/kjingers/Grokking-Notes/wiki/4,-2],-[4,-3],-[2,-0],-[2,-1],-[3,-1))))
print("Topological sort: " +
str(topological_sort(7, [6, 4], [6, 2], [5, 3], [5, 4], [3, 0], [3, 1], [3, 2], [4, 1](/kjingers/Grokking-Notes/wiki/6,-4],-[6,-2],-[5,-3],-[5,-4],-[3,-0],-[3,-1],-[3,-2],-[4,-1))))
main()
Task Scheduling (COurse Schedule)
from collections import deque
def is_scheduling_possible(tasks, prerequisites):
sortedOrder = []
sources = deque()
graph = {i: [] for i in range(tasks)}
inDegrees = {i: 0 for i in range(tasks)}
# Build Graph and inDegrees
for parent, child in prerequisites:
graph[parent].append(child)
inDegrees[child] += 1
# Add all sources to queue
for vertex, numInDegrees in inDegrees.items():
if numInDegrees == 0:
sources.append(vertex)
# While sources: pop source, add to sorted order
# Decrement inDegrees. Add to sources if 0 inDegrees
while sources:
source = sources.popleft()
sortedOrder.append(source)
for vertex in graph[source]:
inDegrees[vertex] -= 1
if inDegrees[vertex] == 0:
sources.append(vertex)
return tasks == len(sortedOrder)
def main():
print("Is scheduling possible: " +
str(is_scheduling_possible(3, [0, 1], [1, 2](/kjingers/Grokking-Notes/wiki/0,-1],-[1,-2))))
print("Is scheduling possible: " +
str(is_scheduling_possible(3, [0, 1], [1, 2], [2, 0](/kjingers/Grokking-Notes/wiki/0,-1],-[1,-2],-[2,-0))))
print("Is scheduling possible: " +
str(is_scheduling_possible(6, [0, 4], [1, 4], [3, 2], [1, 3](/kjingers/Grokking-Notes/wiki/0,-4],-[1,-4],-[3,-2],-[1,-3))))
main()
All Tasks Scheduling Orders
'''
This problem requires topological sort in addition to subsets algorithm.
We will generate the graph and inDegrees and initial sources like usual.
For this one, we will create a function that we will call recursively.
- We will loop through all the sources and call the recursive function for each source.
- After the recursive call, we will backtrack - removing the vertex from the output sortedOrder
- If len(sortedOrder) == tasks, then we have a valid order of tasks, so print
'''
from collections import deque
def print_orders(tasks, prerequisites):
sources = deque()
sortedOrder = []
# 1. Initialize graph and inDegrees
graph = {i: [] for i in range(tasks)}
inDegrees = {i: 0 for i in range(tasks)}
# 2. Build graph and inDegrees
for parent, child in prerequisites:
graph[parent].append(child)
inDegrees[child] += 1
# 3. Add initial sources
for vertex in inDegrees:
if inDegrees[vertex] == 0:
sources.append(vertex)
# 4. Call recursive function with initial sources and empty sortedOrder
print_all_orders_rec(graph, inDegrees, sources, sortedOrder)
def print_all_orders_rec(graph, inDegrees, sources, sortedOrder):
if sources:
# Loop through all current sources.
# We will call recursively to get all combinations where the next vertex
# in the soortedOrder is the current vertex
for vertex in sources:
# Make copy of sources. Remove current vertex
nextSources = deque(sources)
nextSources.remove(vertex)
sortedOrder.append(vertex)
# Update inDegrees. Update nextSources in necessary
for child in graph[vertex]:
inDegrees[child] -= 1
if inDegrees[child] == 0:
nextSources.append(child)
# Call this funtion recursively
print_all_orders_rec(graph, inDegrees, nextSources, sortedOrder)
# Backtrack - Remove current vertex from sortedOrder and replace routes
# By incrementing inDegrees
sortedOrder.pop()
for child in graph[vertex]:
inDegrees[child] += 1
if len(sortedOrder) == len(graph):
print(sortedOrder)
def main():
print("Task Orders: ")
print_orders(3, [0, 1], [1, 2](/kjingers/Grokking-Notes/wiki/0,-1],-[1,-2))
print("Task Orders: ")
print_orders(4, [3, 2], [3, 0], [2, 0], [2, 1](/kjingers/Grokking-Notes/wiki/3,-2],-[3,-0],-[2,-0],-[2,-1))
print("Task Orders: ")
print_orders(6, [2, 5], [0, 5], [0, 4], [1, 4], [3, 2], [1, 3](/kjingers/Grokking-Notes/wiki/2,-5],-[0,-5],-[0,-4],-[1,-4],-[3,-2],-[1,-3))
main()
Alien Dictionary
'''
Similar to typical topological sort problem.
Main difference is that we don't have the prerequisite pairs. To build the graph, we
must compare all adjacent word pairs. Compare letter by letter until a letter is different.
The different character in the left word is a parent, and the different character in the right
word is the child.
'''
from collections import deque
def find_order(words):
sortedOrder = []
sources = deque()
# Initialize Graph and inDegrees
graph = {}
inDegrees = {}
# Make sure each distint character is a key in both maps
for word in words:
for char in word:
graph[char] = []
inDegrees[char] = 0
# Build Graph and inDegrees by comparing adjacent word pairs
for i in range(len(words)- 1):
w1, w2 = words[i], words[i+1]
for j in range(min(len(w1), len(w2))):
parent = w1[j]
child = w2[j]
if parent != child:
if child not in graph[parent]:
graph[parent].append(child)
inDegrees[child] += 1
break
# Add Sources
for vertex in inDegrees:
if inDegrees[vertex] == 0:
sources.append(vertex)
# While sources, process each source
while sources:
source = sources.popleft()
sortedOrder.append(source)
for child in graph[source]:
inDegrees[child] -= 1
if inDegrees[child] == 0:
sources.append(child)
if len(sortedOrder) != len(graph):
return ""
return ''.join(sortedOrder)
def main():
print("Character order: " + find_order(["ba", "bc", "ac", "cab"]))
print("Character order: " + find_order(["cab", "aaa", "aab"]))
print("Character order: " + find_order(["ywx", "wz", "xww", "xz", "zyy", "zwz"]))
main()