Graph & Topology Sort
Graph
- Definition
- Graph is very similar to trees
- Directed v.s. Undirected
- Interview Questions
- DFS, BFS
- DAG (Directed Acyclic Graph)
- Topological sort
Graph Representation
- Graph node
- Adjacent matrices
- |V| vertices, then |V|*|V| matrix of 0s and 1s for the graph.
- Adjacent lists
- |V| vertices, then |V| arraylist, each containing the adjacent vertices.
- 2|E| space for undirected, and |E| space for directed.
GraphNode {
int val;
List<GraphNode> neighbors;
}
Graph Representation
- Adjacent matrices
- Adjacent lists
1 0 0 0 0
0 1 0 1 1
0 0 1 0 0
0 1 0 1 0
0 1 0 0 1
1 0 0 1 0
0 1 0 1 0
1 0 1 0 1
0 1 0 1 0
0 1 0 0 1
1 2,3
2 1,5
3 1
4 5
5 2,4
1 3,5
2 1,5
3 1
4 5
5 1,3,4
Topological Sort
A topological sort (sometimes abbreviated toposort) or topological ordering of a directed graph is a linear ordering of its vertices such that for every directed edge uv from vertex u to vertex v, u comes before v in the ordering.
- Maintain a set of vertices with 0-incoming vertices.
- Keep selecting 0-incoming vertex, remove all its outcoming edges, add it into the set.
- If all vertices are merged into 0-incoming vertices set, then topological sort result is found.
Course Schedule
There are a total of n courses you have to take, labeled from 0 to n - 1.
Some courses may have prerequisites, for example to take course 0 you have to first take course 1, which is expressed as a pair: [0,1]
Given the total number of courses and a list of prerequisite pairs, is it possible for you to finish all courses?
Example:
[1, 0], possible; [[1,0], [0,1]], impossible.
Course Schedule
How would we solve it in real life?
- Draw a directed graph, with prerequisite order.
- Select a course which has no prerequisite, and remove all its prerequisite relationships.
- Repeat the above operation until all courses are scheduled or 0-prerequisite course doesn't exist.
- If there are courses not selected, then the courses cannot be scheduled.
Course Schedule
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
degrees = [0 for _ in range(numCourses)] # how many courses need to finish if we want to take the course
pre = [[] for _ in range(numCourses)] # I am who's the prerequisites
for i, j in prerequisites: # O(E)
degrees[i] += 1
pre[j].append(i)
cur = []
for i in range(numCourses): # O(V)
if degrees[i] == 0: # the courses doesn't need to prerequisites
cur.append(i)
ans = []
while (cur): # O(V + E)
next = []
for i in cur:
for x in pre[i]:
degrees[x] -= 1
if degrees[x] == 0:
next.append(x)
ans += cur
cur = next
return len(ans) == numCourses
# TC, SC: O(V+E)
Alien Dictionary
There is a new alien language which uses the latin alphabet. However, the order among letters are unknown to you. You receive a list of words from the dictionary, where words are sorted lexicographically by the rules of this new language. Derive the order of letters in this language.
For example,
Given the following words in dictionary,
[ "wrt", "wrf", "er", "ett", "rftt"]
The correct order is: "wertf".
Alien Dictionary
What do we need to do?
Use the words to get the priority between two letters
Use topology sort to get the information of the order of these letters
Note:
You may assume all letters are in lowercase.
If the order is invalid, return an empty string.
There may be multiple valid order of letters, return any one of them is fine.
Alien Dictionary
def alienOrder(self, words: List[str]) -> str:
pre = collections.defaultdict(set)
post = collections.defaultdict(set)
for a, b in zip(words, words[1:]):
for i in range(len(a)):
if i >= len(b):
return ""
if a[i] != b[i]:
pre[b[i]].add(a[i])
post[a[i]].add(b[i])
break
allchars = set("".join(words))
q = [x for x in allchars if x not in pre]
res = []
while q:
next_q = []
for cur in q:
res.append(cur)
for neighbor in post[cur]:
pre[neighbor].remove(cur)
if len(pre[neighbor]) == 0:
next_q.append(neighbor)
q = next_q
if len(res) == len(allchars):
return "".join(res)
return ""
Clone Graph
clone an undirected graph. Each node in the graph contains a label and a list of its neighbors.
class UndirectedGraphNode {
int label;
List<UndirectedGraphNode> neighbors;
UndirectedGraphNode(int x) { label = x; neighbors = new ArrayList<UndirectedGraphNode>(); }
};
Clone Graph
Use BFS/DFS to go through all the nodes
Use Hashmap to remember which nodes has been visited
Clone each node with its label and is neighbors
A B
C D
A' B'
C' D'
D''
Clone Graph
def cloneGraph(self, node: 'Node') -> 'Node':
if node is None:
return None
maps = {}
def dfs(node, maps):
if node is None:
return None
if node in maps:
return maps.get(node)
copy = Node(node.val)
maps[node] = copy
for child in node.neighbors:
copy_child = dfs(child, maps)
copy.neighbors.append(copy_child)
return copy
dfs(node, maps)
return maps.get(node)
Reconstruct Itinerary
You are given a list of airline tickets where tickets[i] = [fromi, toi] represent the departure and the arrival airports of one flight. Reconstruct the itinerary in order and return it.
All of the tickets belong to a man who departs from "JFK", thus, the itinerary must begin with "JFK". If there are multiple valid itineraries, you should return the itinerary that has the smallest lexical order when read as a single string.
Reconstruct Itinerary


Input: tickets = [["MUC","LHR"],["JFK","MUC"],["SFO","SJC"],["LHR","SFO"]]
Output: ["JFK","MUC","LHR","SFO","SJC"]
Input: tickets = [["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]]
Output: ["JFK","ATL","JFK","SFO","ATL","SFO"]
Explanation: Another possible reconstruction is ["JFK","SFO","ATL","JFK","ATL","SFO"] but it is larger in lexical order.
Reconstruct Itinerary
All the edges are given. How do we know which way we should go?
Use DFS -> iterate all the possible ways and try to find all the ways
And use a certain way to compare the result to get the best results
How should we store the data -> use Adjacent list
def findItinerary(self, tickets: List[List[str]]) -> List[str]:
graph = collections.defaultdict(list)
for src, des in tickets:
graph[src].append(des)
## sort the the graph for each src
for src in graph:
graph[src].sort()
def dfs(cur, res, graph, n, city):
if n == 0:
res.append(list(cur))
return True
for index, neighbor in enumerate(graph[city]):
cur.append(neighbor)
del graph[city][index]
if dfs(cur, res, graph, n - 1, neighbor):
return True
graph[city].insert(index, neighbor)
cur.pop()
n = len(tickets)
res, cur = [], ["JFK"]
dfs(cur, res, graph, n, "JFK")
return res[0]
def findItinerary(self, tickets: List[List[str]]) -> List[str]:
graph = collections.defaultdict(list)
# Build graph: reverse sort destinations so we can pop smallest next
for src, dst in sorted(tickets, reverse=True):
graph[src].append(dst)
itinerary = []
def visit(airport: str):
while graph[airport]:
next_dest = graph[airport].pop()
visit(next_dest)
# print(airport)
itinerary.append(airport)
visit("JFK")
return itinerary[::-1]
Minimum Height Trees
For a undirected graph with tree characteristics, we can choose any node as the root. The result graph is then a rooted tree. Among all possible rooted trees, those with minimum height are called minimum height trees (MHTs). Given such a graph, write a function to find all the MHTs and return a list of their root labels.
Examples
Given n = 4, edges = [[1, 0], [1, 2], [1, 3]], return [1].
Given n = 6, edges = [[0, 3], [1, 3], [2, 3], [4, 3], [5, 4]], return [3, 4]
0 | 1 / \ 2 3
0 1 2 \ | / 3 | 4 | 5
def findMinHeightTrees(self, n: int, edges: List[List[int]]) -> List[int]:
if n == 1:
return [0]
graph = collections.defaultdict(list)
indegree = [0] * n
for x, y in edges:
graph[x].append(y)
graph[y].append(x)
indegree[x] += 1
indegree[y] += 1
q = []
for i in range(n):
if indegree[i] == 1:
q.append(i)
count = len(q)
seen = set()
while q:
next_level = []
for cur in q:
seen.add(cur)
if len(seen) == n:
return q
for neighbor in graph[cur]:
indegree[neighbor] -= 1
if indegree[neighbor] == 1:
next_level.append(neighbor)
q = next_level
Python [Jo] Graph & Topology Sort
By ZhiTongGuiGu
Python [Jo] Graph & Topology Sort
- 16