声明:本文摘自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时多了些操作,修正加权方法是否值得要具体分析。
转载于:https://blog.51cto.com/searchcoding/1559465