算法第四版 Union-Find

简单的例子,网络中有10个节点,我们用整数数组int[] a = new int[10]代表这些节点,其中数组元素下标代表节点ID。假设初始时这些节点两两独立,相互之间没有连接。我们先连接网络,然后判断两个点是否相连。
1,普通方法
我们用数组元素值代表每个网络的ID号,如果a[i] == a[j],则节点i和节点j在同一个网络上。
首先对数组a进行初始化,假定每个数组元素初始值为节点的编号,即a[i]=i,则会得到
      0 1 2 3 4 5 6 7 8 9
a = 0 1 2 3 4 5 6 7 8 9
假设将点2和3相连,最简单的方式是将3赋给a[2](也可以将2赋给a[3],情况类似)
      0 1 2 3 4 5 6 7 8 9
a = 0 1 3 3 4 5 6 7 8 9
然后将点5和点6相连,同理
      0 1 2 3 4 5 6 7 8 9
a = 0 1 3 3 4 6 6 7 8 9
再将点6和点2相连,这时我们将所有ID为3的网络全部改成6
      0 1 2 3 4 5 6 7 8 9
a = 0 1 6 6 4 6 6 7 8 9
判断两个点是否相连只需要对比两个点的网络ID是否相同
代码如下:

public class UF 
{
	private int[] id;//代表网络中的节点
	private int count;//总网络数
	//构造器,每个数组元素初始值为节点的编号
	public UF(int N)
	{
		count = N;
		id = new int[N];
		for(int i=0;i<N;i++)
			id[i] = i;
	}
	public int count()
	{
		return count;
	}
	//判断两个节点是否相连
	public boolean connected(int p, int q)
	{
		return find(p)==find(q);
	}
	//返回某节点的网络ID
	public int find(int p)
	{
		return id[p];
	}
	//连接节点p和节点q
	public void union(int p, int q)
	{
		int pID = find(p);
		int qID = find(q);
		if(pID == qID)	return;
		//遍历数组,把所有pID改成qID
		for(int i=0;i<id.length;i++)
		{
			if(id[i] == pID)
				id[i] = qID;
		}
		count--;
	}
}

2,quick-find和quick-union算法
在普通方法中,如果需要将点p和点q相连,调用union(p, q)需要遍历数组将所有pID改成qID,无疑非常繁琐。于是有人提出了quick-find算法。
在该算法中,数组元素值并非代表网络ID,而是代表与其相连的上一个节点的编号。而如果一个节点的元素值与其自身的编号相等,则表示它是一个根节点。这种层层相连的关系和树很相似,对于给定的节点,一定能找到其根节点。连接两个节点时需要将第一个节点的根节点的元素值改成第二个节点的根节点编号。
首先初始化数组
      0 1 2 3 4 5 6 7 8 9
a = 0 1 2 3 4 5 6 7 8 9
将点2和3相连
      0 1 2 3 4 5 6 7 8 9
a = 0 1 3 3 4 5 6 7 8 9
将点5和点6相连
      0 1 2 3 4 5 6 7 8 9
a = 0 1 3 3 4 6 6 7 8 9
再将点2和点5相连,先找两个点的根节点,分别为点3和点6,然后将点3的值改成6
      0 1 2 3 4 5 6 7 8 9
a = 0 1 3 6 4 6 6 7 8 9
将点7与点3相连,点3的根节点为点6,因此把点7的值改成6
      0 1 2 3 4 5 6 7 8 9
a = 0 1 3 6 4 6 6 6 8 9
在这里插入图片描述
只需要修改find和union方法即可,代码如下:

//返回某节点的根节点
public int find(int p)
{
	//如果p点不是根节点,则循环查找其上一个节点,直到找到根节点为止
	while(id[p]!=p)
	{
		p = id[p];
	}
	return p;
}
//连接节点p和节点q
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--;
}

3,加权quick-union算法
在最差的情况下,假如数组所有元素形成了一条链,在find方法时依然需要遍历整个数组。注意到在union操作时既可以将p的根节点连接到q的根节点后面,也可以将q的根节点连接到p的根节点后面,我们选择将小树连接到大树后面可以大大提升运算效率。
在这里插入图片描述
代码如下:

public class UF2 
{
	private int[] id;//代表网络中的节点
	private int count;//总网络数
	private int[] sz;//根节点的子节点数
	//构造器
	public UF2(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;
		}
	}
	public int count()
	{
		return count;
	}
	//判断两个节点是否相连
	public boolean connected(int p, int q)
	{
		return find(p)==find(q);
	}
	//返回某节点的根节点
	public int find(int p)
	{
		//如果p点不是根节点,则循环查找其上一个节点,直到找到根节点为止
		while(id[p]!=p)
		{
			p = id[p];
		}
		return p;
	}
	//连接节点p和节点q
	public void union(int p, int q)
	{
		int pRoot = find(p);
		int qRoot = find(q);
		if(pRoot == qRoot)	return;
		//哪个根节点的子节点多,就把该根节点作为合并后的根节点
		if(sz[pRoot] < sz[qRoot])
		{
			id[pRoot] = qRoot;
			sz[qRoot] += sz[pRoot];
		}
		else
		{
			id[qRoot] = pRoot;
			sz[pRoot] += sz[qRoot];
		}
		count--;
	}
}

4,路径压缩算法
路径压缩算法就是在检查节点的同时将它们直接链接到根节点。为此只需修改find方法:

public int find(int p)
{
	int n = p;
	int tmp;
	//如果p点不是根节点,则循环查找其上一个节点,直到根节点为止
	while(id[p]!=p)
	{
		p = id[p];
	}
	//修改路径上的非根节点,把它们直接连接到根节点上
	while(id[n]!=n)
	{
		tmp = n;
		n = id[n];
		id[tmp] = p;
	}
	return p;
}

猜你喜欢

转载自blog.csdn.net/falomsc/article/details/94744757