并查集

并查集是一种高效的数据结构,应用也很广。大一时候简单看了别人的并查集模板,于是生搬硬套过来也A了几道并查集的题目,但是对于其中的一些细节还是不太了解。如今再来看看并查集,有了些新的收获。

初学者推荐一条例题引入并查集。

hdu 1232畅通工程 http://acm.hdu.edu.cn/showproblem.php?pid=1232

首先是初始化根节点par数组和对应的rank数组。如果是简单的题目,rank其实可以省略,但是rank使得并查集算法更高效,且防止了树的退化。

int par[max_n];
int rank[max_n];
void ini(int n)
{
    for(int i=0;i<n;i++)
        {par[i]=i;rank[i]=0;}
}

par[i]存储了i节点的父节点,如果是 par[i]=i 则代表其本身是父节点。

rank[i]存储了i节点的深度作为附加信息。

先介绍第一种find()

int find(int x)
{
    int r=x;
   while(par[r] !=r)
        r=par[r];
   return r;
}
这是最直观的find(),目的只有一个,即为找到父亲节点。

第二种find()

int find(int i)
{
    if (par[i] != i)
    {
        par[i] = find(par[i]);
    }
    return par[i];
}

递归实现,这个方法就是常说的路径压缩,在找寻父亲节点的过程中更新了par[],减少了复杂度。但是递归实现始终有一个通病:容易栈溢出。RE的一般问题都是出现在这儿。

第三种find()

int find(int x)
{
    int r=x;
    while(r!=par[r])
        r=par[r];

    int i=x,j;
    while(par[i]!=r)
    {
        j=par[i];
        par[i]=r;
        i=j;
    }
    return r;
}
既达到路径压缩功能,又防止了栈溢出。这是up主使用的方法,推荐使用。

下面就是unite()函数

void unite(int x,int y)
{
    x=find(x);
    y=find(y);
    if(x==y)
        return ;
    if(rank[x]<rank[y])
        par[x]=y;
    else
    {
        par[y]=x;
    if(rank[x]==rank[y])
        rank[x]++;
    }
}
在unite两个节点x,y时,不仅要做到par的更新,还要更新rank,防止树的退化。

下面方便起见,我定义了一个same函数

bool same(int x,int y)
{
    return find(x)==find(y);
}

判断是否属于同一组。

这种数据结构是比较容易实现的,变形也很多。

下面给出hdu1232的代码,就是上述算法的实现:

#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
using namespace std;
const int max_n=2010;
int par[max_n];
int rank[max_n];
int n;
void ini(int n)
{
    for(int i=0;i<n;i++)
        {par[i]=i;rank[i]=0;}
}
int find(int x)
{
    int r=x;
    while(r!=par[r])
        r=par[r];


    int i=x,j;
    while(par[i]!=r)
    {
        j=par[i];
        par[i]=r;
        i=j;
    }
    return r;
}
void unite(int x,int y)
{
    x=find(x);
    y=find(y);
    if(x==y)
        return ;
    if(rank[x]<rank[y])
        par[x]=y;
    else
    {
        par[y]=x;
    if(rank[x]==rank[y])
        rank[x]++;
    }
}
bool same(int x,int y)
{
    return find(x)==find(y);
}
int main()
{
    int N,M;
    while(scanf("%d",&N)==1)
    {
        if(N==0)
            break;
        ini(max_n);
        scanf("%d",&M);
        int count=N-1;//初始化为N-1条路
        while(M--)
        {
            int a,b;
            scanf("%d%d",&a,&b);
            if(!same(a,b))//每找到两条已经相连的路就少建设一条路
                {
                    unite(a,b);
                    count--;
                }
        }
        printf("%d\n",count);
    }
    return 0;
}
一天又过去了,今天过的怎么样?梦想是不是更远了?

你可能感兴趣的:(并查集)