现在的位置: 首页 > 综合 > 正文

并查集

2014年10月03日 ⁄ 综合 ⁄ 共 4828字 ⁄ 字号 评论关闭

主要是用来处理两个不相交集合的合并和查询的问题,并用一个树根来表示一个集合。

合并操作:要合并两个集合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值相加。

Image:dis-Union.jpg

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;
}

 

 

 

 

 

【上篇】
【下篇】

抱歉!评论已关闭.