1. 程式人生 > >Union-Find並查集初探

Union-Find並查集初探

並查集是一種將同類或者相互聯絡事物歸併並賦予同一類別標籤的資料結構,這在現實世界中應用非常普遍。

顧名思義,並查集包含兩個重要操作Union和Find,Union將兩個物件合併,Find是查詢某一個物件屬於的類別或組份。

因此我們需要如下資料結構:

1.由物件編號(0,1,2...)索引的組份陣列componentID,開始每一個物件都屬於以自己為唯一元素的組分中。

2.記錄組份數count

3.為了讓樹儘量平衡些,需要一個數組來記錄各個節點的深度,讓深度小的節點掛接在深度較大的節點上。

那麼顯然,如果在合併的過程中就將兩個物件的組份ID設為一致,查詢的時候就相當容易;如果在合併的過程中只是將一個物件與另一個物件相連,則查詢的時候需要確定某個物件的始祖物件才能確定其所屬的組份ID。

public class UnionFind {

	/*
	 * 並查集用一個ID來標識一組具有某種傳遞性特徵(導致相同特徵)的物件
	 */
	private int[] componentID;
	private byte[] rank;//rank[i]節點i的深度
	private int count; // number of components

	/*
	 * 初始化:將每個物件看成獨立的組份,並給出該組分的ID(一般是該物件的自己的索引)
	 */
	public UnionFind(int n)
	{
		if(n<0) throw new IllegalArgumentException();
		this.count=n;
		this.componentID=new int[n];
		this.rank=new byte[n];
		for(int i=0;i<n;i++)
		{
			componentID[i]=i;
			rank[i]=0;
		}
	}
	
	//連線兩個節點,根據等級的不同
	public void union(int p,int q){
		int rootP=this.find_recursive(p);
		int rootQ=this.find(q);
		if(rootP==rootQ)
			return;
		
		if(rank[rootP] < rank[rootQ]) componentID[rootP] = rootQ;
        else if (rank[rootP] > rank[rootQ]) componentID[rootQ] = rootP;
        else {
        	componentID[rootQ] = rootP;
            rank[rootP]++;
        }
        count--;
		/*
		if(rank[rootP]<rank[rootQ])
		{
			for(int i=0;i<this.componentID.length && componentID[i]==rootP;i++)
				componentID[i]=rootQ;
		}
			
		else if(rank[rootP]>rank[rootQ])
		{
			for(int i=0;i<this.componentID.length && componentID[i]==rootQ;i++)
				componentID[i]=rootP;
		}
		else
		{
			for(int i=0;i<this.componentID.length && componentID[i]==rootP;i++)
				componentID[i]=rootQ;
            rank[rootP]++;
		}
		count--;
		*/
	}

	//在查詢p節點所屬組份ID過程中會將實際上同屬於一個組份的不同ID修改成相同
	//非遞迴形式
	public int find(int p)
	{
		int n=this.componentID.length;
		if(p<0||p>=n)
			throw new IllegalArgumentException("index " + p + " is not between 0 and " + (n-1));
		while(p!=this.componentID[p])
		{
			this.componentID[p]=componentID[componentID[p]];
			p=componentID[p];
		}
		return p;
		/*
		 * if(p==componentID[p])
		 * 		return p;
		 * 
		 * componentID[p]=find(componentID[p])
		 */
	}
	
	//查詢的遞迴形式
	public int find_recursive(int p)
	{
		if(p==componentID[p])
			return p;
		componentID[p]=find(componentID[p]);
		return componentID[p];
		
	}
	
	public boolean isConnected(int p,int q)
	{
		return this.find(p)==this.find(q);
	}

	int count()
	{
		return this.count;
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		UnionFind uf=new UnionFind(10);
		uf.union(0,5);
		uf.union(1,6);
		uf.union(2,1);
		uf.union(3,4);
		uf.union(4,9);
		uf.union(5,6);
		uf.union(7,2);
		uf.union(8,3);
		uf.union(9,4);
		//容易發現該並查集屬於同一組分的物件的直接ID不一致
		System.out.println(uf.count);
		for(int i=0;i<uf.componentID.length;i++)
			System.out.println("index "+i+" linked to "+uf.componentID[i]);
		for(int i=0;i<uf.rank.length;i++)
			System.out.println("index "+i+" rank is "+uf.rank[i]);
		for(int i=0;i<uf.componentID.length;i++)
			System.out.println(i+" belongs to "+uf.find(i));
	}
}

下面是測試程式碼的測試結果:

2
index 0 linked to 0
index 1 linked to 0
index 2 linked to 0
index 3 linked to 3
index 4 linked to 3
index 5 linked to 0
index 6 linked to 1
index 7 linked to 0
index 8 linked to 3
index 9 linked to 3
index 0 rank is 2
index 1 rank is 1
index 2 rank is 0
index 3 rank is 1
index 4 rank is 0
index 5 rank is 0
index 6 rank is 0
index 7 rank is 0
index 8 rank is 0
index 9 rank is 0
0 belongs to 0
1 belongs to 0
2 belongs to 0
3 belongs to 3
4 belongs to 3
5 belongs to 0
6 belongs to 0
7 belongs to 0
8 belongs to 3
9 belongs to 3