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;

}

Topological Sort

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 vu 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) {
    ArrayList<ArrayList<Integer>> graph = new ArrayList<>();
    for (int i = 0; i < numCourses; i++) {
        graph.add(new ArrayList<Integer>());
    }
    int[] preNum = new int[numCourses];
    for (int i = 0; i < prerequisites.length; i++) {
        graph.get(prerequisites[i][1]).add(prerequisites[i][0]);
        preNum[prerequisites[i][0]]++;
    }
    
    for (int i = 0; i < numCourses; i++) {
        boolean availableCourse = false;
        for (int j = 0; j < numCourses; j++) {
            if (preNum[j] == 0) {
                for  (int k : graph.get(j)) {
                    preNum[k]--;
                }
                availableCourse = true;
                preNum[j] = -1;
                break;
            }
        }
        if (!availableCourse) {
            return false;
        }
    }
    return true;
}

Course Schedule

def canFinish(numCourses, prerequisites):
    graph = [[] for _ in range(numCourses)]
    preNum = [0] * numCourses
    
    for i in range(len(prerequisites)):
        graph[prerequisites[i][1]].append(prerequisites[i][0])
        preNum[prerequisites[i][0]] += 1
    
    for i in range(numCourses):
        availableCourse = False
        for j in range(numCourses):
            if preNum[j] == 0:
                for k in graph[j]:
                    preNum[k] -= 1
                availableCourse = True
                preNum[j] = -1
                break
        if not availableCourse:
            return False
    
    return True

Clone Graph

clone an undirected graph. Each node in the graph contains a label and a list of its neighbors.

 

 
class Node:
    def __init__(self, val = 0, neighbors = None):
        self.val = val
        self.neighbors = neighbors if neighbors is not None else []

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

Clone Graph

	public Node cloneGraph(Node node) {
        if (node == null) {
            return null;
        }
        Map<Node, Node> map = new HashMap<>();
        return dfs(node, map);
    }
    
    private Node dfs(Node node, Map<Node, Node> map) {
        if (node == null) {
            return null;
        }
        if (map.containsKey(node)) {
            return map.get(node);
        }   
        Node copy = new Node(node.val);
        map.put(node, copy);
        
        for (Node neighbor : node.neighbors) {
            copy.neighbors.add(dfs(neighbor, map));
        }
        
        return copy;
	}

Clone Graph

    def cloneGraph(self, node: 'Node') -> 'Node':
        if not node:
            return None
        
        node_map = {}
        
        def dfs(node):
            if node in node_map:
                return node_map[node]
            
            clone = Node(node.val)
            node_map[node] = clone
            
            for neighbor in node.neighbors:
                clone.neighbors.append(dfs(neighbor))
            
            return clone
        
        return dfs(node)

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>> hm = new HashMap<>();
    Set<Character> set = new HashSet<>();
    Queue<Character> queue = new LinkedList<>();
    StringBuilder result = new StringBuilder();
    for (String word: words) {
        char[] wordArray = word.toCharArray();
        for (char c: wordArray) {
            set.add(c);
        }
    }
    for (Character c: set) {
        hm.put(c, new HashSet<Character>());
    }
    for (int i = 0; i < words.length-1; i ++) {
        int minLen = Math.min(words[i].length(), words[i+1].length());
        int j = 0;
        for(j = 0; j < minLen; j ++) {
            if (words[i].charAt(j) != words[i+1].charAt(j)) {
                hm.get(words[i+1].charAt(j)).add(words[i].charAt(j));
                break;
            }
        }
        if (j == minLen && words[i].length() > words[i+1].length()) return "";
    }
    

Alien Dictionary

for(Map.Entry<Character, Set<Character>> entry: hm.entrySet()) {
    if (entry.getValue().isEmpty()) {
        queue.add(entry.getKey());
        result.append(entry.getKey());
    }
}
while(!queue.isEmpty()) {
        Character c = queue.poll();
        for(Map.Entry<Character, Set<Character>> entry: hm.entrySet()) {
            if (entry.getValue().contains(c)) {
                entry.getValue().remove(c);
                if (entry.getValue().isEmpty()) {
                    queue.add(entry.getKey());
                    result.append(entry.getKey());
                }
            }
        }
    }
    String finalResult = result.toString();
    if (finalResult.length() == set.size()) {
        return finalResult;
    } else {
        return "";
    }
}

Alien Dictionary

def alienOrder(words):
    hm = {}
    set = set()
    queue = []
    result = ""
    
    for word in words:
        wordArray = list(word)
        for c in wordArray:
            set.add(c)
    
    for c in set:
        hm[c] = set()
    
    for i in range(len(words)-1):
        minLen = min(len(words[i]), len(words[i+1]))
        j = 0
        for j in range(minLen):
            if words[i][j] != words[i+1][j]:
                hm[words[i+1][j]].add(words[i][j])
                break
        if j == minLen and len(words[i]) > len(words[i+1]):
            return ""

Alien Dictionary

 for c, values in hm.items():
        if len(values) == 0:
            queue.append(c)
            result += c
    
    while len(queue) > 0:
        c = queue.pop(0)
        for key, values in hm.items():
            if c in values:
                values.remove(c)
                if len(values) == 0:
                    queue.append(key)
                    result += key
    
    if len(result) == len(set):
        return result
    else:
        return ""

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

Minimum Height Trees

  • Calculate the height of trees for every node
  • O(|V| * |V|)

Minimum Height Trees

How many Minimum Height Trees can a tree have at most?

     0
     |
     1
    / \
   2   3
     0
     |
     1
     0
     |
     1
    |
    2

At most 2!

Minimum Height Trees

How many Minimum Height Trees can a tree have at most?

We can use a proof by Contradiction

 

If there are more than 2 nodes that are the roots.

We just choose 3 of them. Then these 3 must become a circle somehow. It contradicts with the definition of tree

Minimum Height Trees

How many Minimum Height Trees can a tree have at most?

We can use a proof by Contradiction

m

h-m

h-m

n

Minimum Height Trees

  • Remove all the leaves
  • Update the graph
  • Keep the process until at most two leaves are left.
public List<Integer> findMinHeightTrees(int n, int[][] edges) {
    List<Integer> result = new ArrayList<>();
    if (n < 1) return result;
    ArrayList<HashSet<Integer>> graph = new ArrayList<>(n);
    for (int i = 0; i < n; i++) {
        graph.add(new HashSet<>());
    }
    for (int i = 0; i < edges.length; i++) {
        graph.get(edges[i][0]).add(edges[i][1]);
        graph.get(edges[i][1]).add(edges[i][0]);
    }
    
    Queue<Integer> leaves = new LinkedList<>();
    for (int i = 0; i < n; i++) {
        if (graph.get(i).size() <= 1) {
            leaves.add(i);
        }
    }
    while (n > 2) {
        n -= leaves.size();
        Queue<Integer> newLeaves = new LinkedList<>();
        while (!leaves.isEmpty()) {
            Integer leaf = leaves.poll();
            Integer neighbor = graph.get(leaf).iterator().next();
            graph.get(neighbor).remove(leaf);
            if (graph.get(neighbor).size() == 1) {
                newLeaves.add(neighbor);
            }
        }
        leaves = newLeaves;
    }
    return new ArrayList<>(leaves);
}
def findMinHeightTrees(n, edges):
    if n < 1:
        return []
    graph = [[] for _ in range(n)]
    for edge in edges:
        graph[edge[0]].append(edge[1])
        graph[edge[1]].append(edge[0])
    leaves = []
    for i in range(n):
        if len(graph[i]) <= 1:
            leaves.append(i)
    while n > 2:
        n -= len(leaves)
        newLeaves = []
        for leaf in leaves:
            neighbor = graph[leaf][0]
            graph[neighbor].remove(leaf)
            if len(graph[neighbor]) == 1:
                newLeaves.append(neighbor)
        leaves = newLeaves
    return leaves

Homework

Copy of Copy of 9 Graph & Topology Sort

By ZhiTongGuiGu

Copy of Copy of 9 Graph & Topology Sort

  • 43