目录

并查集

实现:

算法描述:

具体代码实现

问题引入 :畅通工程

代码实现



借鉴于https://www.cnblogs.com/xzxl/p/7226557.html


并查集

并查集由一个整数型的数组和两个函数构成。数组pre[]记录了每个点的前导点是什么,函数find是查找,函数join是合并。

实现:

1. int pre[1000];  这个数组,记录了每个人的上级是谁。从1或者0开始编号(依据题意而定),pre[15]=3就表示15号的上级是3号。如果一个人的上级就是他自己,那说明他就是掌门人了,查找到此为止。

2.find这个函数就是找掌门用的,意义再清楚不过了。

int find(int x)                        //查找x的掌门
{
    int r=x;                           //委托 r 去找掌门
    while(pre[r] != r)                 //如果r的上级不是r自己(也就是说找到的大侠他不是掌门 = =)
        r = pre[r] ;                   // r 接着找他的上级,直到找到掌门为止。
    return  r ;                        //掌门驾到~~~
}

3.join函数,就是在两个点之间连一条线,这样一来,原先它们所在的两个板块的所有点就都可以互通了。

void join(int x,int y)                     
{
    int fx=find(x), fy=find(y);             //查找x和y的上级fx,fy
    if(fx != fy)                             //如果fx和fy不是同一个人
        pre[fx]=fy;                       //使fx的上级为fy,即合并成一个组织了
}

4.路径压缩

建立连接的过程是用join函数两个人两个人地连接起来的,谁当谁的手下完全随机。最后的树状结构会变成什么样,我也完全无法预计,一字长蛇阵也有可能。这样查找的效率就会比较低下。最理想的情况就是所有人的直接上级都是掌门,一共就两级结构,只要找一次就找到掌门了。哪怕不能完全做到,也最好尽量接近。这样就产生了路径压缩算法。

这样,查询中所有涉及到的人物都聚集在一个掌门的直接领导下。每次查询都做了优化处理,所以整个组织树的层数都会维持在比较低的水平上。

算法描述:

一般来说,一个并查集对应三个操作:初始化+查找根结点函数+合并集合函数。

1.初始化

int pre[max];   //集合index的类别,或者用parent表示,用来表示上级是谁
int rank[max];  //集合index的层次,通常初始化为0
//初始化集合
void Make_pre(int i)
{    
    pre[i]=i;   //一个集合的pre都是这个集合自己的标号。没有跟它同类的集合,那么这个集合的源头只能是自己了。 
    rank[i]=0;
}

2.查找函数

//查找集合i(一个元素是一个集合)的源头(递归实现)
int Find_pre(int i)
{
    //如果集合i的父亲是自己,说明自己就是源头,返回自己的标号
   if(pre[i]==i)
       return pre[i];
    //否则查找集合i的父亲的源头
    return  Find_pre(pre[i]);       
}

3.合并集合函数

并查集 (入门)-----畅通工程

void Union(int i,int j)
{
    i=Find_pre(i);
    j=Find_pre(j);
    if(i==j) return ;
    if(rank[i]>rank[j]) pre[j]=i;
    else
    {
        if(rank[i]==rank[j]) rank[j]++;  
        pre[i]=j;
    }
}

具体代码实现

#define N 105
int pre[N];     //每个结点
int rank[N];    //树的高度
//初始化
int init(int n)     //对n个结点初始化
{
    for(int i = 0; i < n; i++){
        pre[i] = i;     //每个结点的上级都是自己
        rank[i] = 1;    //每个结点构成的树的高度为1
    }
}
 
int find_pre(int x)     //查找结点x的根结点
{
    if(pre[x] == x){        //递归出口:x的上级为x本身,即x为根结点
        return x;      
    }
    return find_pre(pre[x]);    //递归查找
}
 
//改进查找算法:完成路径压缩,将x的上级直接变为根结点,那么树的高度就会大大降低
int find_pre(int x)     //查找结点x的根结点
{
    if(pre[x] == x){        //递归出口:x的上级为x本身,即x为根结点
        return x;      
    }
    return pre[x] = find_pre(pre[x]);   //递归查找  此代码相当于 先找到根结点rootx,然后pre[x]=rootx
}
  
void unite(int x,int y)
{
    int rootx, rooty;
    rootx = find_pre(x);
    rooty = find_pre(y);
    if(rootx == rooty){
        return ;
    }
    if(rank(rootx) > rank(rooty)){
        pre[rooty] = rootx;         //令y的根结点的上级为rootx
    }
    else{
        if(rank(rootx) == rank(rooty)){
            rank(rooty)++;
        }
        pre[rootx] = rooty;
    }
} 

问题引入 :畅通工程

Problem Description

某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。问最少还需要建设多少条道路

Input

测试输入包含若干测试用例。每个测试用例的第1行给出两个正整数,分别是城镇数目N ( < 1000 )和道路数目M;随后的M行对应M条道路,每行给出一对正整数,分别是该条道路直接连通的两个城镇的编号。为简单起见,城镇从1到N编号。
注意:两个城市之间可以有多条道路相通,也就是说
3 3
1 2
1 2
2 1
这种输入也是合法的
当N为0时,输入结束,该用例不被处理。

Output

对每个测试用例,在1行里输出最少还需要建设的道路数目。

Sample Input

4 2

1 3

4 3

3 3

1 2

1 3

2 3

5 2

1 2

3 5

999 0

0

Sample Output

1

0

2

998


代码实现

#include<iostream>
using namespace std;
int pre[1010];
int rank_pre[1010];
//初始化
void init(int n)
{
    for(int i=0; i<n; i++)
    {
        pre[i]=i;
        rank_pre[i]=1;
    }
}
//查找函数
int find_pre(int x)
{
    if(pre[x]==x)
        return x;
    return pre[x]=find_pre(pre[x]);
}

//合并函数
void unite(int x,int y)
{
    int fx=find_pre(x);
    int fy=find_pre(y);
    if(fx==fy)
        return;
    if(rank_pre[fx]>rank_pre[fy])
        pre[fy]=x;
    else
    {
        if(rank_pre[fx]==rank_pre[fy])
            rank_pre[fy]++;
        pre[fx]=y;
    }
}
//确定连通分支的个数
int find_num(int n)
{
    int num=0;
    for(int i=0;i<n;i++)
    {
        if(pre[i]==i)
            num++;
    }
    return num;
}
int main()
{
    int m,n;
    while(1)
    {
        cin>>n;
        if(n==0)
            break;
        cin>>m;
        init(n);
        for(int i=0; i<m; i++)
        {
            int x,y;
            cin>>x>>y;
            unite(x-1,y-1);
        }
        int num=find_num(n);
        cout<<num-1<<endl;
    }
    return 0;
}

 

 

 

 

相关文章: