1 并查集的思想:
已知:
1)很多个元素有着一个属性值,这些属性与其他元素的属性值或相同或不相同;
2)我们不知道每个元素的属性值,但知道一些两个元素之间属性值相等的关系——等价关系(有自反性,对称性,传递性);
目标:
根据等价关系将所有元素划分到对应的等价类中去,每个等价类中的元素两两之间都存在着等价关系。
算法:
1)将所有的元素初始化为独立的集合;
2)根据元素间的等价关系,将对应的元素所在的集合合并Union操作。如何合并?我们给每个集合自定义一个集合值来表示其中的元素属性。Union所需的操作即把原来两个分开的集合的两个集合值改为一个相同的集合值则。我们已知等价关系中的两个元素,需要一个Find方法来查询这个集合值,再将这两个集合值修改为一个值;
2.实现:
并查集是可用树来实现,其实也可以其他的实现方法。比如:
1).可用额外一个标记数组来表示属性,而不用代表元素来表示属性。当然,那样Union的复杂度会大大提高O(n),但find的复杂度会降低O(1);
2).可用多个vector来表示不同的集合。集合的合并的实现为:建立新的vector将原来两个vector的元素都拷贝过来。然后销毁原来的两个vector。那么集合值就是vector的地址。这种理解最直观。无疑,这种方法的效率最低。Union和find的复杂度都为O(n);
3)用树来实现集合(这也是集合的最常见的实现数据结构)。则可用树的根节点来表示元素值。这样Union操作大大的简化了,只需让一个集合的根节点指向另一个集合的根节点即可——O(1)。当然,find的复杂则达到了近似O(log2 n)。
树的实现是最理想的。树只是一种逻辑结构,出于方便。我用了STL的关联数组来实现。
class UnionFindSet { public: string find(string str); bool unite(string str1,string str2); void initial(string str1); private: map<string,string> _union_find_set; }; void UnionFindSet::initial(string str1) { _union_find_set[str1]=string("-1"); } string UnionFindSet::find(string str) { string father=str; do{ str=father; father=_union_find_set[str]; }while(father[0]!='-'&&!father.empty()); if(father.empty()) initial(str); return str; } bool UnionFindSet::unite(string ancestor1,string ancestor2) { int size1,size2; if(ancestor1==ancestor2) return false; else{ //C++11 is not support in gcc 4.6 //size1=stoi(_union_find_set[ancestor1]); //size2=stoi(_union_find_set[ancestor2]); size1=atoi(_union_find_set[ancestor1].c_str()); size2=atoi(_union_find_set[ancestor2].c_str()); if(size1>=size2){ _union_find_set[ancestor1]=ancestor2; stringstream ss; ss<<size1+size2; _union_find_set[ancestor2]=ss.str(); } else{ _union_find_set[ancestor2]=ancestor1; stringstream ss; ss<<size1+size2; _union_find_set[ancestor1]=ss.str(); } return true; } }
3.应用
可用并查集来解决很多问题。Kruskal算法,判断图是否含有环。这两个问题的算法都需要归并不同的联通分量。与并查集用于归并集合的用途吻合。
还可用并查集来处理二分图问题。这个问题其实就是判断图是否有包含奇数个节点的环的问题。代码如下,需要添加一个bCircleNodeNumOdd方法:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> //#include <ext/map> #include <map> #include <string> #include <iostream> #include <sstream> using namespace std; //using namespace __gnu_cxx; class UnionFindSet { public: string find(string str); bool unite(string str1,string str2); void initial(string str1); bool bCircleNodeNum(string str1,string str2); private: map<string,string> _union_find_set; }; bool UnionFindSet::bCircleNodeNumOdd(string str1,str2) { //Todo return true; return false; } void UnionFindSet::initial(string str1) { _union_find_set[str1]=string("-1"); } string UnionFindSet::find(string str) { string father=str; do{ str=father; father=_union_find_set[str]; }while(father[0]!='-'&&!father.empty()); if(father.empty()) initial(str); return str; } bool UnionFindSet::unite(string ancestor1,string ancestor2) { int size1,size2; if(ancestor1==ancestor2) return false; else{ //C++11 is not support in gcc 4.6 //size1=stoi(_union_find_set[ancestor1]); //size2=stoi(_union_find_set[ancestor2]); size1=atoi(_union_find_set[ancestor1].c_str()); size2=atoi(_union_find_set[ancestor2].c_str()); if(size1>=size2){ _union_find_set[ancestor1]=ancestor2; stringstream ss; ss<<size1+size2; _union_find_set[ancestor2]=ss.str(); } else{ _union_find_set[ancestor2]=ancestor1; stringstream ss; ss<<size1+size2; _union_find_set[ancestor1]=ss.str(); } return true; } } int main(int argc,char **argv) { int t,m,i,j; scanf("%d",&t); for(i=0;i<t;++i){ m=-1; scanf("%d",&m); UnionFindSet set; bool b_checked=false; for(j=0;j<m;++j){ string pair1,pair2; cin>>pair1>>pair2; if(b_checked) continue; if(set.find(pair1)==set.find(pair2)){ if(set.bCircleNodeNumOdd(pair1,pair2)){ printf("Case #%d: No ",i+1); // cout<<" Same father : "<<set.find(pair1)<<endl; b_checked=true; } } else { set.unite(set.find(pair1),set.find(pair2)); } } if(!b_checked) printf("Case #%d: Yes ",i+1); //printf(" m: %d ; size:%d ",m,set._union_find_set.size()); } return 0; }