1. 程式人生 > >使用並查集UnionFind和優先佇列PriorityQueue實現Kruskal演算法

使用並查集UnionFind和優先佇列PriorityQueue實現Kruskal演算法

拿到題目,先看看UnionFind 和 PriorityQueue 怎麼實現吧,誰讓上課沒好好聽呢。

Kruskal演算法是通過按照權值遞增的順序依次選擇圖中的邊,當邊不處於同一連通分量時加入生成樹,否則捨去此邊選擇下一條代價最小的邊,直到所有頂點都在同一連通分量上。

1.UnionFind並查集

並查集適用於動態連通性問題,比如說最小生成樹時判定兩節點是否在同一連通分量中等等。java實現如下:

UnionFind.java


public class UnionFind {
private int [] id;//每個節點組號
private int [] sz;//每個組的大小
private int count ;//聯通分量數目

//初始化
public  UnionFind(int N){
count = N;
id = new int[N];
sz = new int[N];
for(int i=0;i<N;i++){
id[i] = i; //初始情況下每個節點的組號就是該節點的序號
sz[i] = 1; //初始時每個組大小都為1
}
}
//返回聯通分量數目
public int count(){
return count;
}
//檢查是否聯通,如果是,返回true
public boolean connected(int p,int q){
return find(q)==find(p);
}
//查詢組號,壓縮路徑,使得樹更加扁平化,提高了find的效率
public int find(int p){
while(p != id[p]){//找到它的根節點
//將p節點的父節點設定為它的爺爺節點
id[p] = id[id[p]];
p = id[p];
}
return p;
}
//組合Weighted Quick-Union
public void union(int p,int q){
int pID = find(p);
int qID = find(q);
//如果在同一組直接返回
if(pID==qID)
return;
//不是就組合,將小樹作為大樹的子樹
else{
if(sz[pID]<sz[qID]){
id[pID] = qID;
sz[qID] += sz[pID];
}
else{
id[qID] = pID;
sz[pID] += sz[qID];
}
count -- ;    //聯通分量數目減少1
}
}

}


2.優先佇列

用二叉堆實現的優先佇列在進行大量資料的插入元素和刪除最大、最小元素時效率更高。

PriorityQueue.java


public class PriorityQueue {
public  Segment [] pq = new Segment[40];//pq 為線段類的一個物件陣列
private int N = 0;//佇列元素個數

public PriorityQueue(int maxN){//初始化佇列
for(int i= 0; i<maxN+1; i++){
pq[i] = new Segment() ;
}
}

public boolean isEmpty(){
return N==0 ;
}

public int size(){
return N;
}

public void insert(int v,int i,int j){//插入佇列元素,並按遞減排序
pq[++N].weight = v;
pq[N].x = i ;
pq[N].y = j ;
swim(N); //上浮操作
}

public int delMin(){//由於要為最小生成樹服務,這裡刪除的是最小值
int min = pq[1].weight;
exch(1, N--);
pq[N+1].weight = 9999;//防止末尾元素為空,隨便指定一個較大值
sink(1);
return min;
}
//小的游上去
private  void swim(int k){
while(k > 1 && pq[k/2].weight > pq[k].weight){
exch(k/2,k);
k = k/2;
}
}
//大的沉下去
private void sink(int k){
while(2*k <= N){
int j = 2*k;
if(j<N && pq[j].weight > pq[j+1].weight)
j++;
if(pq[k].weight < pq[j].weight)
break;
exch(k, j);
k = j ;
}
}

private void exch(int i,int j){//線段物件交換,開始時我只交換線段的權重,後來修正
Segment temp = pq[i] ;
pq[i] = pq[j] ;
pq[j] = temp;
}
}


3.由於Kruskal演算法的操作是以線段權重大小為順序,我們要操作的物件是線段,所以需要構造一個簡單的線段類:

Segment.java

package com.zsx;


public class Segment {
public int weight;//線段的權重
public int x; //線段的兩個頂點
public int y;

}

4.總函式:



import java.util.Scanner;


public class Kruskal {
public static void main(String []args){
int N,v,i,j,sum ;
System.out.println("請輸入節點數:");
Scanner in = new Scanner(System.in);
N = in.nextInt();//節點數
UnionFind uf = new UnionFind(N);//建立並查集物件
PriorityQueue weight = new PriorityQueue(N*N);//建立優先佇列物件

System.out.println("請輸入節點編號及他們之間路徑的權重,輸入-1結束:");
while((i = in.nextInt())!= -1){//當輸入-1時停止輸入
int x=0,y=0;
j = in.nextInt();
v = in.nextInt();
weight.insert(v,i,j);//優先佇列中插入節點
}
System.out.println("Kruskal演算法構造的最小生成樹成功:");
for(i=0,sum=0;i< N -1 ;){//最小生成樹中,N個節點最多有N-1條邊
int p = weight.pq[1].x;
int q = weight.pq[1].y;
if(!uf.connected(p,q )){//按照邊的權重從小到大開始選擇,當線段的2個節點不在一個連通分量中時選擇
System.out.println(weight.pq[1].weight+ " "+p +" "+q );
sum += weight.pq[1].weight;
uf.union(p, q);//標記為一個同分量
i++;
}
weight.delMin();
}
System.out.println("權重和:"+sum);

}
}