[LintCode] Graph Valid Tree
Given n
nodes labeled from 0
to n - 1
and a list of undirected
edges (each edge is a pair of nodes), write a function to check whether these edges make up a valid tree.
You can assume that no duplicate edges will appear in edges. Since all edges are undirected
, [0, 1]
is the same as [1, 0]
and thus will not appear together in edges.
Given n = 5
and edges = [[0, 1], [0, 2], [0, 3], [1, 4]]
, return true.
Given n = 5
and edges = [[0, 1], [1, 2], [2, 3], [1, 3], [1, 4]]
, return false.
Algorithm:
For an undirected graph of n vertices, if the number of edges is not n - 1, then this graph can not be a valid tree. This graph is either cyclic in the case of more than n - 1 edges, or disconnected in the case of fewer than n - 1 edges. If there are n - 1 edges, then further check is needed to detect if there is any cycle in the graph. If there is a cycle, then this graph is not a valid tree. If there is no cycles, then it is a valid tree.
Solution 1. BFS, O(V + E) runtime, O(V + E) space
Start from a random node and do a BFS, if all vertices can be reached then we know there can not be any cycles, we have a valid tree.
1 public class Solution { 2 public boolean validTree(int n, int[][] edges) { 3 if(edges.length != n - 1) 4 { 5 return false; 6 } 7 HashMap<Integer, HashSet<Integer>> graph = initializeGraph(n, edges); 8 9 //bfs 10 Queue<Integer> queue = new LinkedList<Integer>(); 11 HashSet<Integer> visitedSet = new HashSet<Integer>(); 12 13 queue.offer(0); 14 visitedSet.add(0); 15 int visitedNodes = 0; 16 while(!queue.isEmpty()) 17 { 18 int node = queue.poll(); 19 visitedNodes++; 20 for(Integer neighbor : graph.get(node)) 21 { 22 if(visitedSet.contains(neighbor)) 23 { 24 continue; 25 } 26 visitedSet.add(neighbor); 27 queue.offer(neighbor); 28 } 29 } 30 return visitedNodes == n; 31 } 32 }
Solution 2. DFS to count all reachable nodes, O(V + E) runtime, O(V + E) space for graph representation, O(n) for the set of visited nodes. The recursion stack space usage should be taken into account too.
1 public class Solution { 2 public boolean validTree(int n, int[][] edges) { 3 if(edges.length != n - 1) 4 { 5 return false; 6 } 7 HashMap<Integer, HashSet<Integer>> graph = initializeGraph(n, edges); 8 HashSet<Integer> visited = new HashSet<Integer>(); 9 visited.add(0); 10 dfs(0, graph, visited); 11 return visited.size() == n; 12 } 13 private HashMap<Integer, HashSet<Integer>> initializeGraph(int n, int[][] edges) 14 { 15 HashMap<Integer, HashSet<Integer>> graph = new HashMap<Integer, HashSet<Integer>>(); 16 for(int i = 0; i < n; i++) 17 { 18 graph.put(i, new HashSet<Integer>()); 19 } 20 for(int i = 0; i < edges.length; i++) 21 { 22 int u = edges[i][0]; 23 int v = edges[i][1]; 24 graph.get(u).add(v); 25 graph.get(v).add(u); 26 } 27 return graph; 28 } 29 private void dfs(int startNode, 30 HashMap<Integer, HashSet<Integer>> graph, 31 HashSet<Integer> visited){ 32 for(Integer neighbor : graph.get(startNode)){ 33 if(!visited.contains(neighbor)){ 34 visited.add(neighbor); 35 dfs(neighbor, graph, visited); 36 } 37 } 38 } 39 }
Solution 3. DFS to check if there is any cycle
1 //Algorithm 3. dfs all nodes to check if there is a cycle in this graph. 2 //O(m + n) time, O(m + n) space, if not considering the recursion space usage 3 public class Solution { 4 public boolean validTree(int n, int[][] edges) { 5 if(n == 0 || edges.length != n - 1) 6 { 7 return false; 8 } 9 HashMap<Integer, HashSet<Integer>> graph = initializeGraph(n, edges); 10 return hasCycleDFS(graph) == false; 11 } 12 private HashMap<Integer, HashSet<Integer>> initializeGraph(int n, int[][] edges) 13 { 14 HashMap<Integer, HashSet<Integer>> graph = new HashMap<Integer, HashSet<Integer>>(); 15 for(int i = 0; i < n; i++) 16 { 17 graph.put(i, new HashSet<Integer>()); 18 } 19 for(int i = 0; i < edges.length; i++) 20 { 21 int u = edges[i][0]; 22 int v = edges[i][1]; 23 graph.get(u).add(v); 24 graph.get(v).add(u); 25 } 26 return graph; 27 } 28 private boolean hasCycleDFS(HashMap<Integer, HashSet<Integer>> graph){ 29 HashSet<Integer> visited = new HashSet<Integer>(); 30 for(Integer node : graph.keySet()){ 31 if(!visited.contains(node)){ 32 boolean flag = hasCycleDFSUtil(node, visited, -1, graph); 33 if(flag){ 34 return true; 35 } 36 } 37 } 38 return false; 39 } 40 private boolean hasCycleDFSUtil(int node, 41 HashSet<Integer> visited, 42 int parent, 43 HashMap<Integer, HashSet<Integer>> graph){ 44 visited.add(node); 45 for(Integer neighbor : graph.get(node)){ 46 //if we skil this check, then when the logic check its parent node 47 //it will mistakenly think a cycle is found. 48 //e.g, A -- B -- C, start dfs from A. When visiting C, its parent B 49 //has been added to the visited set, if we only rely on the visited.contains(B) 50 //check, it will cause this method return true. We should only return true 51 //when its neighbor has been visited and this neighbor is not its parent node 52 if(neighbor == parent){ 53 continue; 54 } 55 if(visited.contains(neighbor)){ 56 return true; 57 } 58 boolean hasCycle = hasCycleDFSUtil(neighbor, visited, node, graph); 59 if(hasCycle){ 60 return true; 61 } 62 } 63 return false; 64 } 65 }
Solution 4. Union Find
1 //Algorithm 4. Union Find, O(m + n) time, O(n) space, m is the number of edges 2 // n is the number of nodes 3 class UnionFind { 4 private int[] father = null; 5 private int count = 0; 6 public UnionFind(int n){ 7 father = new int[n]; 8 count = n; 9 for(int i = 0; i < n; i++){ 10 father[i] = i; 11 } 12 } 13 public int find(int x){ 14 if(father[x] == x){ 15 return x; 16 } 17 return father[x] = find(father[x]); 18 } 19 public void connect(int a, int b){ 20 int root_a = find(a); 21 int root_b = find(b); 22 if(root_a != root_b){ 23 father[root_a] = father[root_b]; 24 count--; 25 } 26 } 27 public int queryCount(){ 28 return count; 29 } 30 } 31 public class Solution { 32 public boolean validTree(int n, int[][] edges) { 33 if(edges.length != n - 1) 34 { 35 return false; 36 } 37 UnionFind uf = new UnionFind(n); 38 for(int i = 0; i < edges.length; i++){ 39 uf.connect(edges[i][0], edges[i][1]); 40 } 41 return uf.queryCount() == 1; 42 } 43 }
Related Problems
Connecting Graph
Connected Component in Undirected Graph