1. 程式人生 > >資料結構09—— 並查集(Union-Find)

資料結構09—— 並查集(Union-Find)

一、關於並查集

並查集(Union-Find)是一種樹型的資料結構,常用於處理一些不相交集合(Disjoint Sets)的合併及查詢問題。並查集(Union-Find)從名字可以看出,主要它涉及兩種基本操作:合併和查詢。這說明,初始時並查集中的元素是不相交的,經過一系列的基本操作(Union),最終合併成一個大的集合。

 

二、並查集的設計和基本實現

 

1.並查集介面的設計

public interface UF {
	
	int getSize();
	boolean isConnected(int p, int q);
	void unionElements(int p, int q);
	
}

 

2.第一版本的並查集實現:基於陣列

package com.zfy.uf;

public class UnionFind1 implements UF {

	private int[] id; // 我們的第一版Union-Find本質就是一個數組

	public UnionFind1(int size) {

		id = new int[size];
		// 初始化, 每一個id[i]指向自己, 沒有合併的元素
		for (int i = 0; i < id.length; i++) {
			id[i] = i;
		}

	}

	@Override
	public int getSize() {
		return id.length;
	}

	// 查詢元素p所對應的集合編號
	// O(1)複雜度
	private int find(int p) {
		if (p < 0 || p >= id.length)
			throw new IllegalArgumentException("p is out of bound.");
		return id[p];
	}

	// 檢視元素p和元素q是否所屬一個集合
	// O(1)複雜度
	@Override
	public boolean isConnected(int p, int q) {

		return find(p) == find(q);
	}

	// 合併元素p和元素q所屬的集合
	// O(n) 複雜度
	@Override
	public void unionElements(int p, int q) {

		int pID = find(p);
		int qID = find(q);

		if (pID == qID) {
			return;
		}

		for (int i = 0; i < id.length; i++) {
			// 合併過程需要遍歷一遍所有元素, 將兩個元素的所屬集合編號合併
			if (id[i] == pID) {
				id[i] = qID;
			}
		}

	}

}

 

3.第二版的並查集

package com.zfy.uf;

public class UnionFind2 implements UF {

	// 我們的第二版Union-Find, 使用一個數組構建一棵指向父節點的樹
	// parent[i]表示第一個元素所指向的父節點
	private int[] parent;

	// 建構函式
	public UnionFind2(int size) {

		parent = new int[size];

		// 初始化, 每一個parent[i]指向自己, 表示每一個元素自己自成一個集合
		for (int i = 0; i < size; i++)
			parent[i] = i;
	}

	@Override
	public int getSize() {
		return parent.length;
	}

	// 查詢過程, 查詢元素p所對應的集合編號
	// O(h)複雜度, h為樹的高度
	private int find(int p) {

		if (p < 0 || p >= parent.length)
			throw new IllegalArgumentException("p is out of bound.");

		// 不斷去查詢自己的父親節點, 直到到達根節點
		// 根節點的特點: parent[p] == p
		while (p != parent[p])
			p = parent[p];
		return p;

	}

	// 檢視元素p和元素q是否所屬一個集合
	// O(h)複雜度, h為樹的高度
	@Override
	public boolean isConnected(int p, int q) {
		return find(p) == find(q);
	}

	// 合併元素p和元素q所屬的集合
	// O(h)複雜度, h為樹的高度
	@Override
	public void unionElements(int p, int q) {
		int pRoot = find(p);
		int qRoot = find(q);

		if (pRoot == qRoot)
			return;

		parent[pRoot] = qRoot;
	}

}

 

4. 基於size的優化

package com.zfy.arithmetic.unionfnd;

/**
 * Created by vincent on 2018/10/15 上午10:13
 */
public class UnionFind3 implements UF {

    private int[] parent; // parent[i]表示第一個元素所指向的父節點
    private int[] sz;     // sz[i]表示以i為根的集合中元素個數

    // 建構函式
    public UnionFind3(int size){

        parent = new int[size];
        sz = new int[size];

        // 初始化, 每一個parent[i]指向自己, 表示每一個元素自己自成一個集合
        for(int i = 0 ; i < size ; i ++){
            parent[i] = i;
            sz[i] = 1;
        }
    }

    @Override
    public int getSize(){
        return parent.length;
    }
    
    // 查詢過程, 查詢元素p所對應的集合編號
    // O(h)複雜度, h為樹的高度
    private int find(int p){
        if(p < 0 || p >= parent.length)
            throw new IllegalArgumentException("p is out of bound.");

        // 不斷去查詢自己的父親節點, 直到到達根節點
        // 根節點的特點: parent[p] == p
        while( p != parent[p] )
            p = parent[p];
        return p;
    }

    // 檢視元素p和元素q是否所屬一個集合
    // O(h)複雜度, h為樹的高度
    @Override
    public boolean isConnected( int p , int q ){
        return find(p) == find(q);
    }

    // 合併元素p和元素q所屬的集合
    // O(h)複雜度, h為樹的高度
    @Override
    public void unionElements(int p, int q){

        int pRoot = find(p);
        int qRoot = find(q);

        if(pRoot == qRoot)
            return;

        // 根據兩個元素所在樹的元素個數不同判斷合併方向
        // 將元素個數少的集合合併到元素個數多的集合上
        if(sz[pRoot] < sz[qRoot]){
            parent[pRoot] = qRoot;
            sz[qRoot] += sz[pRoot];
        }
        else{ // sz[qRoot] <= sz[pRoot]
            parent[qRoot] = pRoot;
            sz[pRoot] += sz[qRoot];
        }
    }
}

5.Main測試方法

 

package com.zfy.arithmetic.unionfnd;

import java.util.Random;

public class Main {

    private static double testUF(UF uf, int m){

        int size = uf.getSize();
        Random random = new Random();

        long startTime = System.nanoTime();


        for(int i = 0 ; i < m ; i ++){
            int a = random.nextInt(size);
            int b = random.nextInt(size);
            uf.unionElements(a, b);
        }

        for(int i = 0 ; i < m ; i ++){
            int a = random.nextInt(size);
            int b = random.nextInt(size);
            uf.isConnected(a, b);
        }

        long endTime = System.nanoTime();

        return (endTime - startTime) / 1000000000.0;
    }

    public static void main(String[] args) {

        // UnionFind1 慢於 UnionFind2
//        int size = 100000;
//        int m = 10000;

        // UnionFind2 慢於 UnionFind1, 但UnionFind3最快
        int size = 100000;
        int m = 100000;

        UnionFind1 uf1 = new UnionFind1(size);
        System.out.println("UnionFind1 : " + testUF(uf1, m) + " s");

        UnionFind2 uf2 = new UnionFind2(size);
        System.out.println("UnionFind2 : " + testUF(uf2, m) + " s");

        UnionFind3 uf3 = new UnionFind3(size);
        System.out.println("UnionFind3 : " + testUF(uf3, m) + " s");
    }
}

 

測試結果:

 

因為本人太懶,一直沒有畫圖,以後一定會改進!!!!!

結束語:合抱之木,生於毫末;九層之臺,起於累土;千里之行,始於足下。

參考:bobobo老師的玩轉資料結構

版權宣告:尊重博主原創文章,轉載請註明出處 https://blog.csdn.net/zfy163520