主要是用来处理两个不相交集合的合并和查询的问题,并用一个树根来表示一个集合。
合并操作:要合并两个集合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;
}