標籤:
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.For example: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.Note: 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.
This problem can be solved by using union find, reference this blog:1190000003791051
複雜度
時間 O(N^M) 空間 O(1)
思路
判斷輸入的邊是否能構成一個樹,我們需要確定兩件事:
這些邊是否構成環路,如果有環則不能構成樹
這些邊是否能將所有節點連通,如果有不能連通的節點則不能構成樹
因為不需要知道具體的樹長什麼樣子,只要知道連通的關係,所以Union Find(並查集)相比深度優先搜尋是更好的方法。我們定義一個並查集的資料結構,並提供標準的四個介面:
具體並查集的原理,參見這篇文章。簡單來講,就是先構建一個數組,節點0到節點n-1,剛開始都各自獨立的屬於自己的集合。這時集合的編號是節點號。然後,每次union操作時,我們把整個並查集中,所有和第一個節點所屬集合號相同的節點的集合號,都改成第二個節點的集合號。這樣就將一個集合的節點歸屬到同一個集合號下了。我們遍曆一遍輸入,把所有邊加入我們的並查集中,加的同時判斷是否有環路。最後如果並查集中只有一個集合,則說明可以構建樹。
注意
因為要判斷是否會產生環路,union方法要返回一個boolean,如果兩個節點本來就在一個集合中,就返回假,說明有環路
1 public class Solution { 2 public boolean validTree(int n, int[][] edges) { 3 unionfind uf = new unionfind(n); 4 for (int i=0; i<edges.length; i++) { 5 if (uf.areConnected(edges[i][0], edges[i][1])) return false; 6 else { 7 uf.union(edges[i][0], edges[i][1]); 8 } 9 }10 return uf.count()==1;11 }12 13 public class unionfind {14 int[] ids; //union id for each node15 int cnt; //the number of independent union 16 17 public unionfind(int size) {18 this.ids = new int[size];19 for (int i=0; i<size; i++) {20 ids[i] = i;21 }22 this.cnt = size;23 }24 25 public boolean union(int i, int j) {26 int src = find(i);27 int dst = find(j);28 if (src != dst) {29 for (int k=0; k<ids.length; k++) {30 if (ids[k] == src) {31 ids[k] = dst;32 }33 }34 cnt--;35 return true;36 }37 return false;38 }39 40 public int find(int i) {41 return ids[i];42 }43 44 public boolean areConnected(int i, int j) {45 return find(i)==find(j);46 }47 48 public int count() {49 return cnt;50 }51 }52 }
Summary:
Dectect cycle in directed graph:
Detect cycle in a directed graph is using a DFS. Depth First Traversal can be used to detect cycle in a Graph. DFS for a connected graph produces a tree. There is a cycle in a graph only if there is a back edge present in the graph. A back edge is an edge that is from a node to itself (selfloop) or one of its ancestor in the tree produced by DFS. In the following graph, there are 3 back edges, marked with cross sign. We can observe that these 3 back edges indicate 3 cycles present in the graph.
To detect a back edge, we can keep track of vertices currently in recursion stack of function for DFS traversal. If we reach a vertex that is already in the recursion stack, then there is a cycle in the tree. The edge that connects current vertex to the vertex in the recursion stack is back edge. We have used recStack[] array to keep track of vertices in the recursion stack.
Detect cycle in undirected graph:
method 1: Union Find The time complexity of the union-find algorithm is O(ELogV).
method 2: DFS + parent node Like directed graphs, we can use DFS to detect cycle in an undirected graph in O(V+E) time. We do a DFS traversal of the given graph. For every visited vertex ‘v’, if there is an adjacent ‘u’ such that u is already visited and u is not parent of v, then there is a cycle in graph. If we don’t find such an adjacent for any vertex, we say that there is no cycle. The assumption of this approach is that there are no parallel edges between any two vertices.
Leetcode: Graph Valid Tree && Summary: Detect cycle in directed graph and undirected graph