並查集在kruskal演算法中應用
阿新 • • 發佈:2019-01-25
在無向圖論問題中,經常需要得到圖的最小生成樹,用於解決這個問題有兩個經典演算法:kruskal和prim,前者用於稀疏圖,後者用於稠密圖。kruskal演算法的核心思想是貪心,按照權值順序,先選取權值最小的邊,再選取權值次小的邊,依此類推,直到所選邊足夠把所有的點連線起來,這時邊數為節點數-1。但有個選邊的前提,那就是待選邊不能和已選邊組成迴路。至此,kruskal演算法要解決的問題便成了圖的連通分支判斷問題。而並查集正是用於求解該問題的有效方法。
並查集是一種樹形資料結構,其實現是定義一個長度為N+1(N為圖的節點個數)的陣列,陣列元素值初始化為下標,表示所有的節點都初始化為由一個單點組成的樹,每個節點都是自身的祖先。那麼並查集如何判斷兩個連通分支是否是一個連通分支(即構成迴路)呢?方法就是查詢兩棵樹的祖先,如果祖先相同,則表示這兩個連通分支是一個連通分支。除了查詢,並查集還有一個方法用來合併兩個連通分支,實現就是把A樹(連通分支)的祖先設定為B樹(連通分支)的祖先,反過來也行。
kruskal演算法通過不斷地選邊,然後查詢這條邊的兩個節點所在的連通分支,再合併分支,最終得到了一顆最小生成樹。演算法的時間複雜度為O(NlogN)。
JAVA實現如下:
public class Kruskal { /** * 查詢分支中某個元素的祖先,當祖先為自身時停止查詢 * @param x * @param branch * @return */ public static int find(int x,int[] branch){ while(x!=branch[x]){ x = branch[x]; } return x; } /** * 合併分支,設branch[a]的祖先為b * @param x * @param branch */ public static void join(int a,int b,int[] branch){ branch[a]=b; } public static void main(String args[]){ Scanner in=new Scanner(System.in); //n表示節點數,m表示邊數 int n = in.nextInt(); int m = in.nextInt(); Edge[] edges = new Edge[m]; Edge edge = null; //讀入每條邊的資訊 for(int i=0;i<m;i++){ edge = new Edge(); edge.a = in.nextInt(); edge.b = in.nextInt(); edge.w = in.nextInt(); edges[i]=edge; } in.close(); Arrays.sort(edges); /** * 並查集初始化為N個分支,每個人都是自己的祖先 */ int[] branch = new int[n+1]; for(int i=0;i<=n;i++){ branch[i]=i; } int selected = 0; int weightTotal = 0; //無需回溯 for(int i=0;i<m && selected<n-1;i++){ //查詢兩個結點的祖先 int rootA = find(edges[i].a,branch); int rootB = find(edges[i].b,branch); if(rootA!=rootB){ //合併兩個分支,可以把A分支的祖先設為B分支的祖先,反之亦可 join(rootA,rootB,branch); weightTotal += edges[i].w; selected ++; } } System.out.println(weightTotal); } } class Edge implements Comparable<Edge>{ public int a; public int b; public int w; @Override public int compareTo(Edge e) { return this.w - e.w; } }