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.html
For example:java
Given n = 5 and edges = [[0, 1], [0, 2], [0, 3], [1, 4]], return true.node
Given n = 5 and edges = [[0, 1], [1, 2], [2, 3], [1, 3], [1, 4]],
return false.數據結構Hint:app
Given n = 5 and edges = [[0, 1], [1, 2], [3, 4]], what should your return? Is this case a valid tree? Show More Hint 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.oop
O( V + E ) 時間 O(V) 空間 ui
並查集(帶路徑壓縮path compression)兩個操做的平攤時間(amortized time)複雜度爲O(log*n),讀做log星n,它增加得極爲緩慢,因此認爲O(1)this
感性的認識一下無向圖中樹的樣子:假設是連通圖,圖若是是樹,那麼樹是無根樹,即誰都是根,因此無根。
這個樹和樹這個數據結構(好比二叉樹)還不同,二叉樹是一個有根樹,有個特殊節點叫根。.net
這道題裏的樹,就是任意兩點有且只有一條路徑可達,這條路徑的每一個邊只走一次。
換句話講,圖想要是樹,得是沒有迴路的連通圖,就是要知足兩個性質:code
是連通圖
無環
通常來說判斷是否有環都在一個連通圖上判斷,若是這個圖有多個連通份量,那就要對每一個連通份量都判斷一下是否有環。都沒有環,那就是森林。只有一個連通份量尚未環,就是樹,無根樹。
無向圖邊的兩端是對稱的,無向圖講究連通這個概念,沒有方向,沒有拓撲,很像集合,因此很是適合用並查集來解決。
解決的方法:
想象一開始這個圖只有頂點,沒有邊,咱們來一條一條的添加邊。
每遇到一條邊,判斷這邊的兩個端點是否在同一個集合裏?集合的property:表示一個連通份量,裏面的任意兩點有且只有一條路徑可達
在的話,表示有環:由於兩個點在一個集合裏就表示這兩個點已經有一條路徑了,如今再加一條路徑,必然構成環。
不在的話,表示不構成環,咱們應該合併這兩個集合:由於加上這條邊,兩個集合就被連起來了,合併成了一個集合
若是想要複雜度爲O(V+E),必須用路徑壓縮。
並查集寫法參考
注意union(p,q)方法:用find()找p,q的老大哥,合併的是p,q的老大哥。
UnionFind Utility(很是intuitive,應該練習在5分鐘內寫完):
class UnionFind { private int[] father; private int count; public UnionFind(int n) { father = new int[n]; count = n; for (int i = 0; i < n; i++){ father[i] = i; } } public int count() { return this.count; } public int find(int p) { int root = father[p]; while (root != father[root]) root = father[root]; //as long as we get here, root is the final dad while (p != root) { int tmp = father[p]; father[p] = root; p = tmp; } return root; } public void union(int p, int q) { int fatherP = find(p); int fatherQ = find(q); if (fatherP != fatherQ) { father[fatherP] = fatherQ; count--; } } }
主程序
public class Solution { public boolean validTree(int n, int[][] edges) { UnionFind uf = new UnionFind(n); for (int[] edge : edges){ int p = edge[0]; int q = edge[1]; if (uf.find(p) == uf.find(q)) return false; else uf.union(p,q); } return uf.count() == 1; } }
O( V + E ) 時間 O(V) 空間
無向圖找環和有向圖找環本質上徹底不一樣。
有向圖找環須要三種顏色。
無向圖找環只須要兩種顏色,就是訪問過的和沒訪問的。
dfs過程當中若是碰到訪問過的節點(固然這個節點不能是來時的節點),就是有環。
public class Solution { public boolean validTree(int n, int[][] edges) { HashSet<Integer> visited = new HashSet<Integer>(); List<List<Integer>> adjList = new ArrayList<List<Integer>>(); for (int i=0; i<n; ++i) adjList.add(new ArrayList<Integer>()); for (int[] edge: edges) { adjList.get(edge[0]).add(edge[1]); adjList.get(edge[1]).add(edge[0]); } if (hasCycle(-1, 0, visited, adjList)) // has cycle? return false; if (visited.size() != n) // is all connected? return false; return true; } private boolean hasCycle(Integer pred, Integer vertex, HashSet<Integer> visited, List<List<Integer>> adjList) { visited.add(vertex); // current vertex is being visited for (Integer succ: adjList.get(vertex)) { // successors of current vertex if (!succ.equals(pred)) { // exclude current vertex's predecessor if (visited.contains(succ)) { return true; // back edge/loop detected! } else { if (hasCycle(vertex, succ, visited, adjList)) { return true; } } } } return false; } }