《算法》union&find算法及改进

算法详细内容参考《算法》第4版1.5案例研究:union-find算法

分析运行结果:

package union;
import java.util.Arrays;
import java.util.Random;

class WeightQuickUnion {
	private int[] id;	//分量id,每个节点所属集合标识
	private int[] size; //以该节点为根其分量大小
	private int count;	//分量数量
	
	//initialize
	public WeightQuickUnion(int N){
		count = N;	//初始化每个节点都是一个分量,union时减少count
		id = new int[N];
		for(int i = 0; i < N; ++i){
			id[i] = i;
		}
		size = new int[N];
		for(int i = 0; i < N; ++i){
			size[i] = 1;
		}
	}
	public int count(){
		return count;
	}
	public boolean connected(int left, int right){
		return find(left) == find(right);
	}
	
	//O(logN) time 返回节点所属集合标识
	public int find(int node){
		while(node != id[node]){
			node = id[node];
		}
		return node;
	}
	
	public void find_quick_union(int left, int right){
		int left_id = find(left);
		int right_id = find(right);
		if(left_id != right_id){
			if(size[left_id] < size[right_id]){
				id[left_id] = right_id;
				size[left_id] += size[right_id];
			}else{
				id[right_id] = left_id;
				size[right_id] += size[left_id];
			}
			--count;
		}
		return ;
	}
}

class WeightQuickCompressUnion {
	private int[] id;	//分量id,每个节点所属集合标识
	private int[] size; //以该节点为根其分量大小
	private int count;	//分量数量
	
	//initialize
	public WeightQuickCompressUnion(int N){
		count = N;	//初始化每个节点都是一个分量,union时减少count
		id = new int[N];
		for(int i = 0; i < N; ++i){
			id[i] = i;
		}
		size = new int[N];
		for(int i = 0; i < N; ++i){
			size[i] = 1;
		}
	}
	public int count(){
		return count;
	}
	public boolean connected(int left, int right){
		return find(left) == find(right);
	}
	
	//O(logN) time 返回节点所属集合标识
	public int find(int node){
		int root = node;
		while(root != id[root]){
			root = id[root];
		}
		while(node != root){
			node = id[node];
			id[node] = root;
		}
		return root;
	}
	
	public void find_quick_union(int left, int right){
		int left_id = find(left);
		int right_id = find(right);
		if(left_id != right_id){
			if(size[left_id] < size[right_id]){
				id[left_id] = right_id;
				size[left_id] += size[right_id];
			}else{
				id[right_id] = left_id;
				size[right_id] += size[left_id];
			}
			--count;
		}
		return ;
	}
}




final class StdRandom {

    private static Random random;    // pseudo-random number generator
    private static long seed;        // pseudo-random number generator seed

    // static initializer
    static {
        // this is how the seed was set in Java 1.4
        seed = System.currentTimeMillis();
        random = new Random(seed);
    }

    // don't instantiate
    private StdRandom() { }

    /**
     * Sets the seed of the pseudorandom number generator.
     * This method enables you to produce the same sequence of "random"
     * number for each execution of the program.
     * Ordinarily, you should call this method at most once per program.
     *
     * @param s the seed
     */
    public static void setSeed(long s) {
        seed   = s;
        random = new Random(seed);
    }

    /**
     * Returns the seed of the pseudorandom number generator.
     *
     * @return the seed
     */
    public static long getSeed() {
        return seed;
    }

    /**
     * Returns a random real number uniformly in [0, 1).
     * @return a random real number uniformly in [0, 1)
     */
    public static double uniform() {
        return random.nextDouble();
    }

    /**
     * Returns a random integer uniformly in [0, n).
     * 
     * @param n number of possible integers
     * @return a random integer uniformly between 0 (inclusive) and {@code n} (exclusive)
     * @throws IllegalArgumentException if {@code n <= 0}
     */
    public static int uniform(int n) {
        if (n <= 0) throw new IllegalArgumentException("argument must be positive");
        return random.nextInt(n);
    }

}

public class Union {
	private int[] id;	//分量id,每个节点所属集合标识
	private int count;	//分量数量
	
	//initialize
	public Union(int N){
		count = N;	//初始化每个节点都是一个分量,union时减少count
		id = new int[N];
		for(int i = 0; i < N; ++i){
			id[i] = i;
		}
	}
	
	public boolean quick_find_connected(int left, int right){
		return quick_find(left) == quick_find(right);
	}
	public boolean find_connected(int left, int right){
		return find(left) == find(right);
	}
	//O(1) time
	public int quick_find(int node){
		return id[node];
	}
	
	//O(n^2) time to union if union N times
	public void quick_find_union(int left, int right){
		int left_id = quick_find(left);     //O(1) time to find
		int right_id = quick_find(right);
		if(left_id != right_id){
			for(int node = 0; node < id.length; ++node){
				if(quick_find(node) == left_id){
					id[node] = right_id;
				}
			}
			--count;
		}
		return ;
	}
	
	//O(logN) time 返回节点所属集合标识
	public int find(int node){
		while(node != id[node]){
			node = id[node];
		}
		return node;
	}
	
	//O(N*treeHeight) time if union N times
	public void find_lazy_quick_union(int left, int right){
		int left_id = find(left);
		int right_id = find(right);
		if(left_id != right_id){
			id[left_id] = right_id;
			--count;
		}
		return ;
	}
	
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		final int E = 90000;
		final int N = 100000;
		int[][] eadges = new int [E][2];
		for(int i = 0; i < E; ++i){
			eadges[i][0] = StdRandom.uniform(N);
			eadges[i][1] = StdRandom.uniform(N);
		}
//		System.out.println(Arrays.deepToString(eadges));
		long startTime;
		long endTime;
		System.out.println("Union");
		Union UF =  new Union(N);
		startTime = System.currentTimeMillis();
		for(int i = 0; i < E; ++i){
			UF.quick_find_union(eadges[i][0], eadges[i][1]);
		}
		endTime = System.currentTimeMillis();
		System.out.println("Run Time:"+(endTime-startTime)+"ms");
		/*for(int p = 0; p < N; ++p){
			for(int q = 0; q < N; ++q)
				System.out.print(UF.quick_find_connected(p,q)+" ");
		}*/
//		System.out.println("");
		System.out.println(UF.count);
		
		System.out.println("LazyWeight");
		Union LazyUF = new Union(N);
		startTime = System.currentTimeMillis();
		for(int i = 0; i < E; ++i){
			LazyUF.find_lazy_quick_union(eadges[i][0], eadges[i][1]);
		}
		endTime = System.currentTimeMillis();
		System.out.println("Run Time:"+(endTime-startTime)+"ms");
		/*for(int p = 0; p < N; ++p){
			for(int q = 0; q < N; ++q)
				System.out.print(LazyUF.find_connected(p,q)+" ");
		}*/
//		System.out.println("");
		System.out.println(LazyUF.count);
		
		System.out.println("Weight");
		WeightQuickUnion WQUF = new WeightQuickUnion(N);
		startTime = System.currentTimeMillis();
		for(int i = 0; i < E; ++i){
			WQUF.find_quick_union(eadges[i][0], eadges[i][1]);
		}
		endTime = System.currentTimeMillis();
		System.out.println("Run Time:"+(endTime-startTime)+"ms");
		/*for(int p = 0; p < N; ++p){
			for(int q = 0; q < N; ++q)
				System.out.print(WQUF.connected(p, q)+" ");
		}*/
//		System.out.println("");
		System.out.println(WQUF.count());
		
		System.out.println("Weight & Compress");
		WeightQuickCompressUnion WCUF = new WeightQuickCompressUnion(N);
		startTime = System.currentTimeMillis();
		for(int i = 0; i < E; ++i){
			WCUF.find_quick_union(eadges[i][0], eadges[i][1]);
		}
		endTime = System.currentTimeMillis();
		System.out.println("Run Time:"+(endTime-startTime)+"ms");
		/*for(int p = 0; p < N; ++p){
			for(int q = 0; q < N; ++q)
				System.out.print(WCUF.connected(p, q)+" ");
		}*/
//		System.out.println("");
		System.out.println(WCUF.count());
	}
}

打印出来的结果:

Union
Run Time:5248ms
20303
LazyWeight
Run Time:739ms
20303
Weight
Run Time:736ms
20303
Weight & Compress
Run Time:19ms
20303
调大边数E

final int E = 99000;
final int N = 100000;

Union
Run Time:6076ms
16619
LazyWeight
Run Time:1296ms
16619
Weight
Run Time:1271ms
16619
Weight & Compress
Run Time:19ms
16619
调小边数E

final int E = 1000;
final int N = 100000;

Union
Run Time:43ms
99000
LazyWeight
Run Time:0ms
99000
Weight
Run Time:1ms
99000
Weight & Compress
Run Time:0ms
99000

union、LazyWeight、Weight、Weight & Compress对应教材quick-find、quick-union、加权quick-union、路径压缩的加权quick-union算法。

边数比较小时图比较稀疏,输出结果中99000表示一共99000个分量,而总共节点为100000,quick-union中每棵树高度都不大所以运行起来比加权quick-union更快,因为quick-union中多了求树节点数量、将小树指向大树的步骤。而增大边数时加权quick-union效果比quick-union更好,因为加权quick-union可以保证树高度最多为logN,N为节点数。

挺有用的结论,把小树的根指向大树的根可以保证树高度小于logN,证明:


小树节点总数N1,高度为h1,大树节点总数N2,高度为h2,;将小树根指向大树根节点会造成小树节点高度加1

h1+1 <= log N1 + 1 = log(N1+N1) <= log(N1 + N2)

大树节点高度不变

所以合并之后的树高度不大于log(N1 + N2)

又单个节点高度为0,数学归纳法即知

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值