# 零基础学并查集算法

并查集是我暑假从高手那里学到的一招，觉得真是太精妙的设计了。以前我无法解决的一类问题竟然可以用如此简单高效的方法搞定。不分享出来真是对不起party了。（party：我靠，关我嘛事啊？我跟你很熟么？）

4 2 1 3 4 3

int pre[1000 ];

int find(int x)                                                                                                         //查找根节点

int r=x;

while ( pre[r ] != r )                                                                                              //返回根节点 r

r=pre[r ];

int i=x , j ;

while( i != r )                                                                                                        //路径压缩

{

j = pre[ i ]; // 在改变上级之前用临时变量  j 记录下他的值

pre[ i ]= r ; //把上级改为根节点

i=j;

}

return r ;

}

void join(int x,int y)                                                                                                    //判断x y是否连通，

//如果已经连通，就不用管了 //如果不连通，就把它们所在的连通分支合并起,

{

int fx=find(x),fy=find(y);

if(fx!=fy)

pre[fx ]=fy;

}

http://i3.6.cn/cvbnm/6f/ec/f4/1e9cfcd3def64d26ed1a49d72c1f6db9.jpg

int find(int x)                                                                  //查找我（x）的掌门

{

int r=x;                                                                       //委托 r 去找掌门

while (pre[r ]!=r)                                                        //如果r的上级不是r自己（也就是说找到的大侠他不是掌门 = =）

r=pre[r ] ;                                                                   // r 就接着找他的上级，直到找到掌门为止。

return  r ;                                                                   //掌门驾到~~~

}

void join(int x,int y)                                                                   //我想让虚竹和周芷若做朋友

{

int fx=find(x),fy=find(y);                                                       //虚竹的老大是玄慈，芷若MM的老大是灭绝

if(fx!=fy)                                                                               //玄慈和灭绝显然不是同一个人

pre[fx ]=fy;                                                                           //方丈只好委委屈屈地当了师太的手下啦

}

http://i3.6.cn/cvbnm/60/98/92/745b3eac68181e4ee1fa8d1b8bca38bc.jpg

hdu1232

 1 #include<iostream>
2 using namespace std;
3 int  pre[1050];
4 bool t[1050];               //t 用于标记独立块的根结点
5 int Find(int x)
6 {
7     int r=x;
8     while(r!=pre[r])
9         r=pre[r];
10
11     int i=x,j;
12     while(pre[i]!=r)
13     {
14         j=pre[i];
15         pre[i]=r;
16         i=j;
17     }
18     return r;
19 }
20 void mix(int x,int y)
21 {
22     int fx=Find(x),fy=Find(y);
23     if(fx!=fy)
24     {
25         pre[fy]=fx;
26     }
27 }
28 int main()
29 {
30     int N,M,a,b,i,j,ans;
31     while(scanf("%d%d",&N,&M)&&N)
32     {
33         for(i=1;i<=N;i++)          //初始化
34             pre[i]=i;
35
36         for(i=1;i<=M;i++)          //吸收并整理数据
37         {
38             scanf("%d%d",&a,&b);
39             mix(a,b);
40         }
41         memset(t,0,sizeof(t));
42         for(i=1;i<=N;i++)          //标记根结点
43         {
44             t[Find(i)]=1;
45         }
46         for(ans=0,i=1;i<=N;i++)
47             if(t[i])
48                 ans++;
49
50         printf("%d\n",ans-1);
51
52     }
53     return 0;
54 }

//以下为原文附的代码:
//回到开头提出的问题，我的代码如下：
#include <bits/stdc++.h>
using namespace std;
int pre[1000];
int find(int x)
{
int r=x;
while (pre[r ]!=r)
r=pre[r ];
int i=x; int j;
while(i!=r)
{
j=pre[i ];
pre[i ]=r;
i=j;
}
return r;
}
int main()
{
int n,m,p1,p2,i,total,f1,f2;
while(scanf("%d",&n) && n)         //读入n，如果n为0，结束
{                                                    //刚开始的时候，有n个城镇，一条路都没有 //那么要修n-1条路才能把它们连起来
total=n-1;
//每个点互相独立，自成一个集合，从1编号到n //所以每个点的上级都是自己
for(i=1;i<=n;i++) { pre[i ]=i; }                //共有m条路
scanf("%d",&m);
while(m--)
{ //下面这段代码，其实就是join函数，只是稍作改动以适应题目要求
//每读入一条路，看它的端点p1，p2是否已经在一个连通分支里了
scanf("%d %d",&p1,&p2);
f1=find(p1);
f2=find(p2);
//如果是不连通的，那么把这两个分支连起来
//分支的总数就减少了1，还需建的路也就减了1
if(f1!=f2)
{
pre[f2 ]=f1;
total--;
}
//如果两点已经连通了，那么这条路只是在图上增加了一个环 //对连通性没有任何影响，无视掉
}
//最后输出还要修的路条数
printf("%d\n",total);
}
return 0;
}

• 网络连接判断：

• 变量名等同性(类似于指针的概念)

• 给出两个节点，判断它们是否连通，如果连通，不需要给出具体的路径
• 给出两个节点，判断它们是否连通，如果连通，需要给出具体的路径

for(int i = 0; i < size; i++)
id[i] = i;


• 查询节点属于的组

• 判断两个节点是否属于同一个组

• 连接两个节点，使之属于同一个组

• 获取组的数目

API

Quick-Find 算法：

 1 public class UF
2 {
3     private int[] id; // access to component id (site indexed)
4     private int count; // number of components
5     public UF(int N)
6     {
7         // Initialize component id array.
8         count = N;
9         id = new int[N];
10         for (int i = 0; i < N; i++)
11             id[i] = i;
12     }
13     public int count()
14     { return count; }
15     public boolean connected(int p, int q)
16     { return find(p) == find(q); }
17     public int find(int p)
18     { return id[p]; }
19     public void union(int p, int q)
20     {
21         // 获得p和q的组号
22         int pID = find(p);
23         int qID = find(q);
24         // 如果两个组号相等，直接返回
25         if (pID == qID) return;
26         // 遍历一次，改变组号使他们属于一个组
27         for (int i = 0; i < id.length; i++)
28             if (id[i] == pID) id[i] = qID;
29         count--;
30     }
31 }

Quick-Union 算法：

 1 private int find(int p)
2 {
3     // 寻找p节点所在组的根节点，根节点具有性质id[root] = root
4     while (p != id[p]) p = id[p];
5     return p;
6 }
7 public void union(int p, int q)
8 {
9     // Give p and q the same root.
10     int pRoot = find(p);
11     int qRoot = find(q);
12     if (pRoot == qRoot)
13         return;
14     id[pRoot] = qRoot;    // 将一颗树(即一个组)变成另外一课树(即一个组)的子树
15     count--;
16 }

 1 public void union(int p, int q)
2 {
3     // Give p and q the same root.
4     int pRoot = find(p);
5     int qRoot = find(q);
6     if (pRoot == qRoot)
7         return;
8     id[pRoot] = qRoot;  // 将一颗树(即一个组)变成另外一课树(即一个组)的子树
9     count--;
10 }

id[pRoot] = qRoot 或者是 id[qRoot] = pRoot

for (int i = 0; i < N; i++)
id[i] = i;    // 每个节点的组号就是该节点的序号


    for (int i = 0; i < N; i++)
sz[i] = 1;    // 初始情况下，每个组的大小都是1


而在进行合并的时候，会首先判断待合并的两棵树的大小，然后按照上面图中的思想进行合并，实现代码：

 1 public void union(int p, int q)
2 {
3     int i = find(p);
4     int j = find(q);
5     if (i == j) return;
6     // 将小树作为大树的子树
7     if (sz[i] < sz[j]) { id[i] = j; sz[j] += sz[i]; }
8     else { id[j] = i; sz[i] += sz[j]; }
9     count--;
10 }

Quick-Union  Weighted Quick-Union 的比较：

find方法的执行过程中，不是需要进行一个while循环找到根节点嘛？如果保存所有路过的中间节点到一个数组中，然后在while循环结束之后，将这些中间节点的父节点指向根节点，不就行了么？但是这个方法也有问题，因为find操作的频繁性，会造成频繁生成中间节点数组，相应的分配销毁的时间自然就上升了。那么有没有更好的方法呢？还是有的，即将节点的父节点指向该节点的爷爷节点，这一点很巧妙，十分方便且有效，相当于在寻找根节点的同时，对路径进行了压缩，使整个树结构扁平化。相应的实现如下，实际上只需要添加一行代码：

 1     private int find(int p)
2     {
3         while (p != id[p])
4         {
5             // 将p节点的父节点设置为它的爷爷节点
6             id[p] = id[id[p]];
7             p = id[p];
8         }
9         return p;
10     }  

 Algorithm Constructor Union Find Quick-Find N N 1 Quick-Union N Tree height Tree height Weighted Quick-Union N lgN lgN Weighted Quick-Union With Path Compression N Very near to 1 (amortized) Very near to 1 (amortized)

posted @ 2017-02-26 17:05  Angel_Kitty  阅读(...)  评论(...编辑  收藏