声明:本文摘自algorithm in c 一书,

并查集:在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。

该算法可以用于解决:连通性问题。

例如:判断计算机网络中两台主机是否连通(查找),如果不连通,就想办法连通(并集,即把两台主机并到一个集合中,两台主机所在集合的Union).


下述中:

×××数组 vec_pair 代表 节点

输入对p,q:判断p,q节点之间是否连通,如果连通,继续处理下一对,否则连通之。

版本1 快速查找:

该方法能快速判读是否连通,但连通的过程稍慢,即快查,慢并。

该方法判断是否连通:两节点值是否相同,vec_pair[p]==vec_pair[q],如果相同,则在同一集合,即连通,否则反之。

并操作:扫描整个数组,把节点值为vec_pair[p]的节点均修改为vec_pair[q],或者反之


该方法用一个×××数组代表节点,

初始化时认为每个节点都是不连通的,表示不连通的方式为,每个节点值为节点所在的下标,自然,所有下标都不同,亦均不通。

vector<int> vec_pair(N);
	for (int i = 0; i < N; i++)
		vec_pair[i] = i;
bool slow_union(vector<int>::size_type p, vector<int>::size_type q, vector<int> &vec_pair)
{
	vector<int>::size_type temp = vec_pair[p];
	vector<int>::size_type tempq = vec_pair[q];
	vector<int>::size_type size = vec_pair.size();

	for (vector<int>::size_type i = 0; i < size; ++i)
	{
		if (vec_pair[i] == temp)
			vec_pair[i]=tempq;
	}
	return true;
}
bool quik_search(vector<int>::size_type p, vector<int>::size_type q, vector<int> &vec_pair)
{
	return vec_pair[p] == vec_pair[q];
}
int main
{
	while (cin>>p>>q)
	{
		if (slow_search(p,q,vec_pair,rootp,rootq))
			continue;
		quik_union(rootp, rootq, vec_pair);
		copy(vec_pair.begin(), vec_pair.end(), ostream_iterator<int>(cout," "));
		cout << endl;
	}
}

由版本一只需一次判断即可得出是否连通,而并集操作却需扫描整个数组,如果有M对,N个节点,则并集操作所需时间为MN。如果N很大(百万),并集操作仍需优化

版本2 快速并集:

在树形结构中,两个节点直接连通,则两个节点之间有分支,即属于同一颗树。为了判断两个节点是否在同一集合(连通),只需跟踪每个节点的父节点,直到指向自身的节点(根节点)。当两个节点查找的过程中得到相同的节点,即可认为是相通的,属于同一集合。


数组初始化后:每个节点指向自身,即,每个节点均是根节点

查找操作:跟踪父节点,直至根节点,如果p,q的根节点相同,则连通,否则,不连通;当然也可以找到中间的某个节点时相同节点时即可判断为连通,但是怎么样才能找到中间相同节点以后再说。

并操作:把p所在的树当中q所在树的子树,即把q的根节点下标,赋值给p节点的根节点。

bool slow_search(int p, int q, vector<int> &vec_pair,int &rootp,int &rootq)

{

//vector<int>::size_type rootp, rootq;

for (rootp = p; rootp != vec_pair[rootp]; rootp = vec_pair[rootp]);

for (rootq = q; rootq != vec_pair[rootq]; rootq = vec_pair[rootq]);

return rootp == rootq;

}

bool quik_union(int rootp, int rootq, vector<int> &vec_pair)

{

vec_pair[rootp] = rootq;

return true;

}


该版本当在最坏情况下,输入1-2,2-3,3-4...等时,连接的树为单支,查找次数

(1+2+3+...N)/N=(N-1)/2,即M对所需时间为MN/2


修正:把节点数少的树作为子树。

添加数组记录子树中节点个数即可。

版本3 加权快速并集:

vector<int> vec_pair(N),sz(N);

for (int i = 0; i < N; i++)

{

vec_pair[i] = i;

sz[i] = 1;

}

bool slow_search(int p, int q, vector<int> &vec_pair,int &rootp,int &rootq)

{

//vector<int>::size_type rootp, rootq;

for (rootp = p; rootp != vec_pair[rootp]; rootp = vec_pair[rootp]);

for (rootq = q; rootq != vec_pair[rootq]; rootq = vec_pair[rootq]);

return rootp == rootq;

}

bool quik_union(int rootp, int rootq, vector<int> &vec_pair)

{

vec_pair[rootp] = rootq;

return true;

}


该版本最坏每次查找均<lgN(以2为底)

版本4:压缩路径

如果能让每个节点直接指向根节点,那就更好了,方法:在查找时使用路径压缩,让子节点指向父节点的父节点。,随着查找次数增多,树会逐渐压平。

修改

bool slow_search(int p, int q, vector<int> &vec_pair,int &rootp,int &rootq)

{

//vector<int>::size_type rootp, rootq;

for (rootp = p; rootp != vec_pair[rootp]; rootp = vec_pair[rootp])

    vec_pair[rootp]=vec_pair[vec_pair[rootp]];

for (rootq = q; rootq != vec_pair[rootq]; rootq = vec_pair[rootq])

    vec_pair[rootq]=vec_pair[vec_pair[rootq]];

return rootp == rootq;

}


总结:

快速查找,快速并集均不适合large N,加权方法比较好,

而压缩路径方法由于在search时多了些操作,修正加权方法是否值得要具体分析。