主要是用来处理两个不相交集合的合并和查询的问题,并用一个树根来表示一个集合。
合并操作:要合并两个集合S1和S2,那么只需要把S1的根的父亲设为S2的根即可。优化方案:将深度小的树成为深度大的子树,称为启发式合并。
查询操作:查找一个元素属于哪个集合,只需顺着叶子到根结点的找到该元素所在的根结点即可。优化:找到该元素的根结点以后将该元素的的父亲设为根节点--路径压缩。 由于深度经常变化,所以我们不适用深度作为启发函数值,而是用rank,开始时初始化rank为1,当两个相同的rank树合并时随便选一颗树作为新根,并把它的rank值相加后赋给父节点,否则rank值大的拥有新根,它的rank值等于两颗树的rank值之和,通过rank值可以保知道每一个集合中的元素个数,比方说下面这幅图中第一个{c,e,h,b}中元素个数为4,即:rank=4,后一个{f,d,g}的rank值为3,组合起来后的元素个数就为7个,即rank值相加。
a图为两个不相交集合,b图为合并后Father(b):=Father(g)
涉及知识点:欧拉回路------一笔画问题,题目链接:NYOJ 42(一笔画问题)
一个图形要能一笔画完成必须符合两个条件,即图形是封闭联通的和图形中的奇点(与奇数条边相连的点)个数为0或2。 即为欧拉回路。
顶点与指数:设一个平面图形是由有限个点及有限条弧组成的,这些点称为图形的顶点,从任一顶点引出的该图形的弧的条数,称为这个顶点的指数。
奇顶点:指数为奇数的顶点。
偶顶点:指数为偶数的顶点。
初始化:把每个结所在点的集合初始化本身。
void Init(int x){ father[x]=x;//father[x]表示x的父结点 rank[x]=1;//rank[x]表示x的秩 }
查找u所在的集合:
int Find(int u)//查找u的根结点 { while(u!=father[u]) //路径压缩,找到公共祖先 /*比方说1的父结点的经过变化是2,2的父结点为4,4的父结点为5,那么1的根结点就为1!=father(1)=2;所以u=2,而father(2)=4; 2!=father(2);所以u=4,继续查找father(4)=5;所以u=5;说明1的根结点就为5~~返回 */ u=father[u]; //cout<<u<<endl; return u;//返回根结点 }
合并两个集合:
void Union(int u,int v) { u=Find(u); v=Find(v); if(u==v) return ;//说明已经构成了环,则不做处理 if(rank[u]>rank[v])//否则如果u的秩大于v的秩,说明u的深度大于v的深度,此时由启发式合并有将v接在u的后面 { rank[u]+=rank[v]; father[v]=u;//将v的父结点改为u } else //反之,将u接在v的后面,将u的父结点改为u { rank[v]+=rank[u]; father[u]=v; } }
以这种方法连通的要求是任意顶点的父节点的rank值都为n--顶点数,可以这样写:rank[Find(1)]==n,任意取一个值即可。
下面的这个模板是转载的:博客链接:http://blog.acmol.com/?p=418
template<int MAX> class UnionFindSet { public: UnionFindSet(){Clear();} int Union(int a,int b) { int u1=Find(a),u2=Find(b); if(num[u1]>num[u2]) { num[u1]+=num[u2]; return father[u2]=u1; } else { num[u2]+=num[u1]; return father[u1]=u2; } } int Find(int a) { return father[a]==-1?a:(father[a]=Find(father[a])); } void Clear() { memset(father,-1,sizeof(father)); fill(num,num+MAX,1); } private: int father[MAX]; int num[MAX]; };
我的模板:nyist 42(一笔画问题)
#include<iostream> #include<cstring> #include<cstdio> using namespace std; const int MAX=1010; int indgree[MAX],father[MAX],rank[MAX],n,m;//father[x]为x的父结点 void Init() { for(int i=1;i<=m;i++)//初始化 { father[i]=i; rank[i]=0; } } int Find(int u)//查找u的根结点 { return father[u]==u?u:Find(father[u]);//路径压缩 } void Union(int u,int v) { u=Find(u); v=Find(v); if(u==v) return ; if(rank[u]>rank[v]) father[v]=u; else { if(rank[u]==rank[v]) rank[v]++; father[u]=v; } } int main() { int k,u,v; scanf("%d",&k); while(k--) { scanf("%d%d",&n,&m); memset(indgree,0,sizeof(indgree)); Init(); for(int i=1;i<=m;i++) { scanf("%d%d",&u,&v); indgree[u]++;//u的入度 indgree[v]++; Union(u,v);//合并两个点 } int sum=0; for(int i=1;i<=n;i++) if(indgree[i]%2) sum++; int flag=1,temp=father[1]; for(int i=1;i<=n;i++) if(temp!=father[i]) flag=0; if((sum==0||sum==2)&&flag) puts("Yes"); else puts("No"); } return 0; }
下面是标程,可以看下,我个人比较趋近c++做法:
#include<iostream> #include<vector> using namespace std; class Visit //邻接表形式判断一个图中某点可达的顶点数目 { public: Visit(vector<vector<int> > &ListGraph):listGraph(ListGraph){} int VisitNumber(int n) //判断一个图中某顶点能到达的顶点数目(有向图与无向图皆可用) { isVisited.assign(listGraph.size(),false); return _VisitNumber(n); } private: int _VisitNumber(int n) { isVisited[n]=true; int num=1; for(vector<int>::iterator it=listGraph[n].begin();it!=listGraph[n].end();++it) if(!isVisited[*it]) num+=_VisitNumber(*it); return num; } vector<vector<int> > &listGraph; vector<bool> isVisited; }; class CountDegree //统计各种度数的顶点的个数 { public: CountDegree(vector<vector<int> >& listGraph):listGraph(listGraph){} vector<int>& GetDegree() { if(!_degreeNumber.empty()) return _degreeNumber; else _degreeNumber.assign(listGraph.size(),0); for(vector<vector<int> >::iterator it=listGraph.begin();it!=listGraph.end();++it) { _degreeNumber[it->size()]++; } return _degreeNumber; } private: vector<int> _degreeNumber; //用以记录各种度的顶点的个数 vector<vector<int> >& listGraph; }; int main() { int n; cin>>n; while(n--) { int v,e,a,b; cin>>v>>e; vector<vector<int> > g(v); for(int i=0;i!=e;i++) { cin>>a>>b; g[a-1].push_back(b-1); g[b-1].push_back(a-1); } if(Visit(g).VisitNumber(0)!=v) cout<<"No"<<endl; else { CountDegree cd(g); vector<int>& d=cd.GetDegree(); int num=0; for(int i=1;i<d.size();i+=2) { num+=d[i]; } if(num==0 || num==2) cout<<"Yes"<<endl; else cout<<"No"<<endl; } } return 0; }
我的c++做法:
#include<iostream> #include<cstring> using namespace std; const int MAX=1010; int n,m; class UnionFindSet{ public: UnionFindSet(){} void CLR() { for(int i=1;i<=m;i++) { father[i]=i; rank[i]=1; } } void Union(int ,int ); int Rank(int u){return rank[u];} int Find(int u){return father[u]==u?u:Find(father[u]);} private: int father[MAX]; int rank[MAX]; }; inline void UnionFindSet::Union(int u,int v) { u=Find(u); v=Find(v); if(u==v) return ; if(rank[u]>rank[v]) { rank[u]+=rank[v]; father[v]=u; } else { rank[v]+=rank[u]; father[u]=v; } } class Indgree{ public: Indgree(){memset(indgree,0,sizeof(indgree));} void Dgree(int u,int v){indgree[u]++;indgree[v]++;} int OddIndgree(); private: int indgree[MAX]; }; int Indgree::OddIndgree() { int sum=0; for(int i=1;i<=n;i++) if(indgree[i]%2) sum++; return sum; } int main() { int k,u,v; cin>>k; while(k--) { UnionFindSet U; Indgree I; cin>>n>>m; U.CLR(); for(int i=1;i<=m;i++) { cin>>u>>v; U.Union(u,v); I.Dgree(u,v); } if((I.OddIndgree()==0||I.OddIndgree()==2)&&U.Rank(U.Find(1))==n) cout<<"Yes"<<endl; else cout<<"No"<<endl; } return 0; }