并查集小记

并查集:

并查集,一种树型的数据结构,处理一些不相交集合的合并及查询问题。比如问题:某个家族人员过于庞大,要判断两个人是否是亲戚,不太容易。现给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。
规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。

从基本实现集合的结构出发:

1.单纯的快速查找:

若id相同则在一个集合中,下图中,( 2, 3, 4, 9 )为一集合, 3 和 6 不在一个集合中

合并集合时,需逐个比较将两个集合的 id 统一,慢

缺陷:

合并慢

==================================================

2.单纯的快速合并:

上图,3 的祖先是 9, 5 的祖先是 6,则 3 和 5 不在一个集合中。

若要合并 3 和 5 所在集合,则任意选择一个集合的祖先接到另一个集合的祖先上去,其他节点信息不动。

如下合并 3, 5:

快速合并的过程图:

缺陷:

树增高,退化。

===================================================

3.按重合并:

将节点数小的集合拼接到节点数大的集合中去,只修改小集合的根节点的父亲,其他节点父亲不动。按重合并能保证树的平缓增长。

==================================================

4.路劲压缩:

如若找 9 的祖先,则从 9 到 0 的路径上的所有节点拼接到0上面。

=================================================

5.按重合并 加上 路径压缩(并查集):

过程图如下

C++ 代码:

#include <iostream>
#include <string.h>
using namespace std;
#define NODE_SIZE 100

int set_weights[NODE_SIZE];
int parent[NODE_SIZE];

void init(){
    for( int i = 0; i < NODE_SIZE; ++i ){
        set_weights[i] = 1;
        parent[i] = i;
    }
}

// 寻找改点的祖先 当该点的值 等于 本身的父亲 才是集合的根,返回
int find_ancestor( int i ){
    if( i != parent[i] )
        parent[i] = find_ancestor( parent[i] );
    return parent[i];
}

void merge_set( int a, int b ){
    int set_a = find_ancestor( a );
    int set_b = find_ancestor( b );

    if( set_a == set_b )
        return;
    // 将重量小的集合拼接到重量大的集合上
    if( set_weights[set_a] >= set_weights[set_b] ){
        set_weights[set_a] += set_weights[set_b];
        // 更新重量小的集合的根的父亲
        parent[set_b] = set_a;
    }
    else{
        set_weights[set_b] += set_weights[set_a];
        parent[set_a] = set_b;
    }
}

int main(){
    init();
    merge_set( 3, 4 );
    for( int i = 0; i <= 9; ++i )
        cout << parent[i] << " ";
    cout << endl;

    merge_set( 4, 9 );
    for( int i = 0; i <= 9; ++i )
        cout << parent[i] << " ";
    cout << endl;

    merge_set( 8, 0 );
    for( int i = 0; i <= 9; ++i )
        cout << parent[i] << " ";
    cout << endl;

    merge_set( 2, 3 );
    for( int i = 0; i <= 9; ++i )
        cout << parent[i] << " ";
    cout << endl;

    merge_set( 5, 6 );
    for( int i = 0; i <= 9; ++i )
        cout << parent[i] << " ";
    cout << endl;

    merge_set( 5, 9 );
    for( int i = 0; i <= 9; ++i )
        cout << parent[i] << " ";
    cout << endl;

    merge_set( 7, 3 );
    for( int i = 0; i <= 9; ++i )
        cout << parent[i] << " ";
    cout << endl;

    merge_set( 4, 8 );
    for( int i = 0; i <= 9; ++i )
        cout << parent[i] << " ";
    cout << endl;

    merge_set( 6, 1 );
    for( int i = 0; i <= 9; ++i )
        cout << parent[i] << " ";
}

运行结果:

应用:

一个很经典的物理模型, N * N 的格子,每个格子有两种状态vacant“空白”或者occupied“被占用”,网格能被过滤,当且仅当顶部和底部能被空白的格子链接。如下,蓝色的是“空白”,白色的是“占用”,则第一幅图是能过滤的,第二幅图是不能被过滤的。

现在知道vacant状态的格子在图中出现的概率 P* 能够影响到图能否被过滤,现在要求 P*。

P < P* ,几乎都不能被过滤,

P > P* ,几乎都能被过滤。

(这个问题乍一看和Union-Find没什么关系)

但可这么做:

1.将格子全部初始化为被占用

2.实现 make_site_vacant() 操作,用于将邻接的格子用 Union 操作联系在一起

3.让所有处于顶部和底部的格子变为vacant

4.再随机将其他的格子变为vacant,直到能够find( 顶部,底部 )

5.估算出P*

并查集小记

时间: 2024-10-05 05:32:06

并查集小记的相关文章

CodeForces 745C Hongcow Builds A Nation 并查集

题意: 给了你n个城市 m条边 k个政府 每个政府管辖的区域内不能和其他政府的区域有相连 即政府之间不存在路径 问你在维护这种关系的同时 最多再加多少条边 思路: 先找出来每个联通块 再找出来没有归属的孤立的点 把他们都放到最大的联通块里 然后每个联通块之间的点两两连边是n*(n-1)/2条边 最后算出来的ans-m就好了 (看别人的博客学了一个max_element 1 #include<bits/stdc++.h> 2 #define cl(a,b) memset(a,b,sizeof(a

并查集(个人模版)

并查集: 1 int find(int a) 2 { 3 int r=a; 4 while(f[r]!=r) 5 r=f[r]; 6 int i=a; 7 int j; 8 while(i!=r) 9 { 10 j=f[i]; 11 f[i]=r; 12 i=j; 13 } 14 return r; 15 } 16 int merge(int a,int b) 17 { 18 int A,B; 19 A=find(a); 20 B=find(b); 21 if(A!=B) 22 { 23 f[B

并查集应用

题目描述: One way that the police finds the head of a gang is to check people's phone calls. If there is a phone call between A and B, we say that A and B is related. The weight of a relation is defined to be the total time length of all the phone calls

【bzoj3674】 可持久化并查集加强版

http://www.lydsy.com/JudgeOnline/problem.php?id=3674 (题目链接) 题意 维护并查集3个操作:合并:回到完成第k个操作后的状态:查询. Solution 其实就是用主席树的叶子节点维护并查集的可持久化数组fa[]. 细节 终于认识到了按秩合并的强大,单纯写个路径压缩Re飞,写了路径压缩+按秩合并比单纯的按秩合并每快多少→_→ 代码 // bzoj3674 #include<algorithm> #include<iostream>

BZOJ1015[JSOI2008]星球大战starwar[并查集]

1015: [JSOI2008]星球大战starwar Time Limit: 3 Sec  Memory Limit: 162 MBSubmit: 5253  Solved: 2395[Submit][Status][Discuss] Description 很久以前,在一个遥远的星系,一个黑暗的帝国靠着它的超级武器统治者整个星系.某一天,凭着一个偶然的机遇,一支反抗军摧毁了帝国的超级武器,并攻下了星系中几乎所有的星球.这些星球通过特殊的以太隧道互相直接或间接地连接. 但好景不长,很快帝国又重

HDU 5606 tree 并查集

tree 把每条边权是1的边断开,发现每个点离他最近的点个数就是他所在的连通块大小. 开一个并查集,每次读到边权是0的边就合并.最后Ans?i??=size[findset(i)],size表示每个并查集根的size Ans_i=size[findset(i)],sizeAns?i??=size[findset(i)],size表示每个并查集根的sizesize. #include<cstdio> #include<cstring> #include<algorithm>

HDU 5441 离线处理 + 并查集

题意:给n个节点m条带权值边的无向图.然后q个问题,每次询问点对的数目,点对需要满足的条件是:1)连通:2)其路径的最大权值不能超过询问值. 分析:如果没次询问一次,dfs一次,很可能超时,因此可以用并查集.离线处理,把边按权值排序,把问题按大小排序.然后离线的过程就是不断向图中加边的过程. 比如样例如下: 然后离线处理,排完序后将会是一条一条的加边:问题也排了序,因此是个累加过程... 1 #include <cstdio> 2 #include <iostream> 3 #in

poj1988 Cube Stacking(并查集

题目地址:http://poj.org/problem?id=1988 题意:共n个数,p个操作.输入p.有两个操作M和C.M x y表示把x所在的栈放到y所在的栈上(比如M 2 6:[2 4]放到[1 6]上为[2 4 1 6]),C x为输出x下面有几个数. 思路:并查集每个集合以栈最下面的数为根,维护两个数组num[x]表示x所在集合节点总数,count[x]表示x下方节点个数.每次查找压缩路径的时候更新count(换父节点的时候每轮都把父节点的count加给儿子,就可以一直更新到x所在栈

习题:过路费(kruskal+并查集+LCA)

过路费  [问题描述]在某个遥远的国家里,有 n 个城市.编号为 1,2,3,…,n.这个国家的政府修 建了 m 条双向道路,每条道路连接着两个城市.政府规定从城市 S 到城市 T 需 要收取的过路费为所经过城市之间道路长度的最大值.如:A 到 B 长度为 2,B 到 C 长度为 3,那么开车从 A 经过 B 到 C 需要上交的过路费为 3. 佳佳是个做生意的人,需要经常开车从任意一个城市到另外一个城市,因此 他需要频繁地上交过路费,由于忙于做生意,所以他无时间来寻找交过路费最低 的行驶路线.然