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
public boolean canFinish(int numCourses, int[][] prerequisites) {
int[] degrees = new int[numCourses]; // how many courses need to finish if we want to take the course
List<List<Integer>> pre = new ArrayList<>(); // I am who's the prerequisites
// Initialize the pre list
for (int i = 0; i < numCourses; i++) {
pre.add(new ArrayList<>());
}
// Populate degrees and pre lists
for (int[] pair : prerequisites) { // O(E)
int course = pair[0];
int preCourse = pair[1];
degrees[course]++;
pre.get(preCourse).add(course);
}
List<Integer> cur = new ArrayList<>();
for (int i = 0; i < numCourses; i++) { // O(V)
if (degrees[i] == 0) { // the courses doesn't need prerequisites
cur.add(i);
}
}
List<Integer> ans = new ArrayList<>();
while (!cur.isEmpty()) { // O(V + E)
List<Integer> next = new ArrayList<>();
for (int i : cur) {
for (int x : pre.get(i)) {
degrees[x]--;
if (degrees[x] == 0) {
next.add(x);
}
}
}
ans.addAll(cur);
cur = next;
}
return ans.size() == numCourses;
}
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
public String alienOrder(String[] words) {
Map<Character, Set<Character>> pre = new HashMap<>();
Map<Character, Set<Character>> post = new HashMap<>();
// Build the graph
for (int i = 0; i < words.length - 1; i++) {
String a = words[i];
String b = words[i + 1];
boolean foundDifference = false;
for (int j = 0; j < Math.min(a.length(), b.length()); j++) {
if (a.charAt(j) != b.charAt(j)) {
pre.putIfAbsent(b.charAt(j), new HashSet<>());
post.putIfAbsent(a.charAt(j), new HashSet<>());
pre.get(b.charAt(j)).add(a.charAt(j));
post.get(a.charAt(j)).add(b.charAt(j));
foundDifference = true;
break;
}
}
// If we finished a longer word and didn't find a difference, the order is invalid
if (!foundDifference && a.length() > b.length()) {
return "";
}
}
// Get all unique characters
Set<Character> allChars = new HashSet<>();
for (String word : words) {
for (char c : word.toCharArray()) {
allChars.add(c);
}
}
// Initialize the queue with characters that have no prerequisites
Queue<Character> q = new LinkedList<>();
for (char c : allChars) {
if (!pre.containsKey(c)) {
q.add(c);
}
}
StringBuilder res = new StringBuilder();
// Perform topological sort
while (!q.isEmpty()) {
char cur = q.poll();
res.append(cur);
if (post.containsKey(cur)) {
for (char neighbor : post.get(cur)) {
pre.get(neighbor).remove(cur);
if (pre.get(neighbor).isEmpty()) {
q.add(neighbor);
}
}
}
}
// Check if the topological sort is valid
if (res.length() == allChars.size()) {
return res.toString();
}
return "";
}
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
public class Solution {
public Node cloneGraph(Node node) {
if (node == null) {
return null;
}
Map<Node, Node> maps = new HashMap<>();
return dfs(node, maps);
}
private Node dfs(Node node, Map<Node, Node> maps) {
if (node == null) {
return null;
}
if (maps.containsKey(node)) {
return maps.get(node);
}
Node copy = new Node(node.val);
maps.put(node, copy);
for (Node neighbor : node.neighbors) {
Node copyNeighbor = dfs(neighbor, maps);
copy.neighbors.add(copyNeighbor);
}
return copy;
}
}
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
public List<String> findItinerary(List<List<String>> tickets) {
Map<String, List<String>> graph = new HashMap<>();
for (List<String> ticket : tickets) {
String src = ticket.get(0);
String dest = ticket.get(1);
graph.putIfAbsent(src, new ArrayList<>());
graph.get(src).add(dest);
}
// Sort the destinations for each source lexicographically
for (List<String> destList : graph.values()) {
Collections.sort(destList);
}
List<String> res = new ArrayList<>();
LinkedList<String> cur = new LinkedList<>();
cur.add("JFK");
dfs(cur, res, graph, tickets.size(), "JFK");
return res;
}
private boolean dfs(LinkedList<String> cur, List<String> res, Map<String, List<String>> graph, int n, String city) {
if (n == 0) {
res.addAll(new ArrayList<>(cur));
return true;
}
List<String> neighbors = graph.getOrDefault(city, new ArrayList<>());
for (int i = 0; i < neighbors.size(); i++) {
String neighbor = neighbors.get(i);
cur.add(neighbor);
neighbors.remove(i);
if (dfs(cur, res, graph, n - 1, neighbor)) {
return true;
}
neighbors.add(i, neighbor);
cur.removeLast();
}
return false;
}
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]
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
public List<Integer> findMinHeightTrees(int n, List<List<Integer>> edges) {
if (n == 1) {
return Collections.singletonList(0);
}
Map<Integer, List<Integer>> graph = new HashMap<>();
int[] indegree = new int[n];
// Build the graph and indegree array
for (List<Integer> edge : edges) {
int x = edge.get(0);
int y = edge.get(1);
graph.putIfAbsent(x, new ArrayList<>());
graph.putIfAbsent(y, new ArrayList<>());
graph.get(x).add(y);
graph.get(y).add(x);
indegree[x]++;
indegree[y]++;
}
Queue<Integer> q = new LinkedList<>();
for (int i = 0; i < n; i++) {
if (indegree[i] == 1) {
q.add(i);
}
}
int count = q.size();
Set<Integer> seen = new HashSet<>();
while (!q.isEmpty()) {
List<Integer> nextLevel = new ArrayList<>();
for (int cur : q) {
seen.add(cur);
if (seen.size() == n) {
return new ArrayList<>(q);
}
for (int neighbor : graph.get(cur)) {
indegree[neighbor]--;
if (indegree[neighbor] == 1) {
nextLevel.add(neighbor);
}
}
}
q = new LinkedList<>(nextLevel);
}
return new ArrayList<>();
}
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
Homework
Copy of Copy of [GoValley-Fred] Graph & Topology Sort
By ZhiTongGuiGu
Copy of Copy of [GoValley-Fred] Graph & Topology Sort
- 41