动态连通性----union-find算法

等价关系:定义(p,q)表示p与q相连,是一种等价关系,具有自反性,对称性以及传递性。

等价关系定义了等价类,给定多个整数对,判断其是否属于同一类,即是否具有动态连通性。

从集合的角度在处理(p,q)时,判断其是否属于不同的集合,如果属于不同的集合则将p以及p所属的集合进行合并。

给定多个整数对,可以确定多个动态连通分量,定义数据结构

                                            Union-find API

Quick-Find

利用数组id来表示每个元素所在的分量Id,判断两个元素p,q是否属于同一分量,则判断id[p]==id[q]即可。

        //某个元素所在分量序号
	public int find(int p){
		return id[p];
	}

	public boolean connected(int p, int q) {	
		return find(p) == find(q);
	}

当执行union操作时,如果属于不同分量,则将p所在分量的所有元素id分量索引全部改为p所在分量Id

                                                                       Quick find overview

        //如果p,q处于不同分量中,则将p,q分量合并,将p所在分量所有元素改为q分量Id
	//合并union性能糟糕 
	public void union(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;
			}
		}
		count--;
	}

代码如下所示:

public class QuickFindUF {
	
	//分量Id  id[p]=x表示元素p所在的分量Id为x
	private int[] id;
	//分量数量
	private int count;

	//初始化N个元素N个分量
	public QuickFindUF(int N) {
		count = N;
		id = new int[N];
		for(int i=0; i<N; i++){
			id[i] = i;
		}
	}
	
	//某个元素所在分量序号
	public int find(int p){
		return id[p];
	}

	public boolean connected(int p, int q) {	
		return find(p) == find(q);
	}

	//如果p,q处于不同分量中,则将p,q分量合并,将p所在分量所有元素改为q分量Id
	//合并union性能糟糕 
	public void union(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;
			}
		}
		count--;
	}
	
	public int count(){
		return count;
	}

}

find方法性能很好,但是union方法为线性,当输入N个整数对时,整体算法来O(N*N)级别。

给出测试如下:

public static void main(String[] args) {
		QuickFindUF uf = new QuickFindUF(5);
		uf.union(0, 1);
		uf.union(0, 2);
		uf.union(0, 3);
		uf.union(0, 4);
	}

id数组变化如下所示:

[0, 1, 2, 3, 4]
[1, 1, 2, 3, 4]
[2, 2, 2, 3, 4]
[3, 3, 3, 3, 4]
[4, 4, 4, 4, 4]

Quick-Union

quick-find算法union方法性能糟糕,进行改进。将id数组看成是父连接数组,即对于x来说,id[x]=p,表示p与x处于同一分量中。

find方法根据p=id[p]不断寻找,直到找个某个根节点

        //某个元素所在分量根元素序号
	public int find(int p){
		while(p != id[p]){
			p = id[p];
		}
		return p;
	}

union合并方法,则首先找到两个元素的根节点,如果根节点不同,则将p所在根节点指向q所在根节点

实际上id数组保存多颗树,每个分量利用父连接组织成一棵树,利用树的根节点来表示某个元素所在的分量

                                                              Quick union overview

        //如果p,q处于不同分量中,则将p,q分量合并
	//合并算法效率较高 但是find效率较低
	public void union(int p, int q) {
		int pRoot = find(p);
		int qRoot = find(q);
		if(pRoot == qRoot) return;
		//将p分量根元素变为q所在根元素
		id[pRoot] = qRoot;
		count--;
	}

代码如下:

public class QuickUnionUF {
	
	//同一个分量中另一元素的名称 例如id[p]=x表示p与x处于同一分量
	//id数组用父连接表示一片森林
	private int[] id;
	//分量数量
	private int count;

	//初始化N个元素N个分量
	public QuickUnionUF(int N) {
		count = N;
		id = new int[N];
		for(int i=0; i<N; i++){
			id[i] = i;
		}
	}
	
	//某个元素所在分量根元素序号
	public int find(int p){
		while(p != id[p]){
			p = id[p];
		}
		return p;
	}

	public boolean connected(int p, int q) {	
		return find(p) == find(q);
	}

	//如果p,q处于不同分量中,则将p,q分量合并
	//合并算法效率较高 但是find效率较低
	public void union(int p, int q) {
		int pRoot = find(p);
		int qRoot = find(q);
		if(pRoot == qRoot) return;
		//将p分量根元素变为q所在根元素
		id[pRoot] = qRoot;
		count--;
	}
	
	public int count(){
		return count;
	}

}

union会将两个树不断合并,最坏情况下,树会蜕变成链表,导致find算法查找效率低下。

加权Quick-Union

加权Quick-Union主要改进如下:在union合并两棵树时,将小树合并到大树中,避免树的高度过度增长

                                                        Weighted quick union overview

利用sz数组来保存每颗树的元素个数

代码如下:

public class WeightedQuickUnionUF {
	
	//同一个分量中另一元素的名称
	//id数组用父连接表示一片森林
	private int[] id;
	//分量数量
	private int count;
	
	//各个根节点对应分量大小
	private int[] sz;

	//初始化N个元素N个分量
	public WeightedQuickUnionUF(int N) {
		count = N;
		id = new int[N];
		for(int i=0; i<N; i++){
			id[i] = i;
		}
		sz = new int[N];
		for(int i=0; i<N; i++){
			sz[i] = 1;
		}
	}
	
	//某个元素所在分量根元素序号
	public int find(int p){
		while(p != id[p]){
			p = id[p];
		}
		return p;
	}

	public boolean connected(int p, int q) {	
		return find(p) == find(q);
	}

	//如果p,q处于不同分量中,则将p,q分量合并
	//合并算法效率较高 但是find效率较低
	public void union(int p, int q) {
		int i = find(p);
		int j = find(q);
		if(i == j) return;
		//将小树的根节点连接到大树的根节点
		if(sz[i] < sz[j]){
			id[i] = j;
			sz[j] += sz[i];
		}else{
			id[j] = i;
			sz[i] += sz[j];
		}
		count--;
	}
	
	public int count(){
		return count;
	}

}

执行union时,判断两个树的元素大小,将小树合并到大树中,避免树的高度过度增加,保证算法在对数性能级别。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值