04-树5. File Transfer--并查集

  对于一个集合常见的操作有:判断一个元素是否属于一个集合;合并两个集合等等。而并查集是处理一些不相交集合(Disjoint Sets)的合并及查询问题的有利工具。

  并查集是利用树结构实现的。一个集合用一棵树来表示,而多个集合便是森林。并查集中的“并”是将两个集合合并即两棵树合并成一颗树;“查”是查找一个元素属于哪个集合,即查找一个节点属于哪棵树。思路如下:

  • 查:通过节点找寻父节点,一直向上查找直到根节点,返回根节点,而根节点代表唯一的那棵树;
  • 并:先查找到两个节点所在的树,如果在同一棵树中(即查找到的根节点相同),则直接返回;否则将一棵树作为子树连到另一棵树上,即将一个根节点作为另一个根节点的儿子。

  那么问题来了,应该将哪个根节点作为儿子呢?简单的想法是随便都可以,所以在编程实现中,要么一直让前一个根节点作为儿子,要么一直让后一个根节点作为儿子。这种实现的优点是实现简单,代码简洁短小。但是要知道相对短的代码不一定效率高。在这个问题上,这种做法容易让树失去平衡,因为可能会将较大树作为较小树的子树,使树的深度增大。事实上,更自然的想法是将较小树作为较大树的子树,这样合并操作不会增加树的深度(当然如果两棵树同样大一定会增加),使树相对平衡。

  集合没有精确定义,通常把一些互不相同的东西放在一起所形成的整体就叫做集合。至于为什么放在一起,是根据具体问题的需求,比如把有公路相连的城镇放在一个集合中(连通性问题)。确定性,互异性,无序性是集合的三大性质。由于集合的性质,通常可以和自然数一一对应。因此,用数组实现并查集非常方便且巧妙:数组下标(从1开始)作为树节点,数组下标对应的值作为父节点。由于根节点没有父节点,故不妨令它对应数组的值为非正数。而如何比较两棵树谁大谁小,一个非常巧妙的技巧是让根节点对应的数组的值(非正数)的绝对值作为这棵树的深度。这样做代码不仅简洁,而且节省空间。具体实现的代码如下:

int father[N] = {0};      //初始化,所有节点都是根节点,深度为0;N为最大可能节点数
int n;                         //n为实际节点数
/*查找函数*/
int Find(int father[], int n, int x)
{
    int root;
    for (root = x; father[root] > 0; root = father[root]);  //向上遍历直到根节点退出循环
    return root;    //返回根节点
}
/*合并函数*/
void Union(int father[], int n, int a, int b)
{
    int ARoot, BRoot;
    ARoot = Find(father, n, a);    //找到a节点所在树的根节点
    BRoot = Find(father, n, b);    //找到b节点所在树的根节点
    if (ARoot == BRoot)
        return;
    else if (father[ARoot] > father[BRoot]) //B树深度大于A树
    {
        /*树的深度不变*/
        father[ARoot] = BRoot;              //将A树指向B树
    }
    else
    {
        if (father[ARoot] == father[BRoot])//A,B两棵树深度相同
            father[ARoot]--;    //树的深度加1,根节点对应数组的值(非正数)的绝对值为这棵树的深度
        father[BRoot] = ARoot;
    }
}

下面是一个考察并查集的练习,题目来源:http://www.patest.cn/contests/mooc-ds/04-%E6%A0%915

We have a network of computers and a list of bi-directional connections. Each of these connections allows a file transfer from one computer to another. Is it possible to send a file from any computer on the network to any other?

Input Specification:

Each input file contains one test case. For each test case, the first line contains N (2<=N<=104), the total number of computers in a network. Each computer in the network is then represented by a positive integer between 1 and N. Then in the following lines, the input is given in the format:

I c1 c2

where I stands for inputting a connection between c1 and c2; or

C c1 c2

where C stands for checking if it is possible to transfer files between c1 and c2; or

S

where S stands for stopping this case.

Output Specification:

For each C case, print in one line the word "yes" or "no" if it is possible or impossible to transfer files between c1 and c2, respectively. At the end of each case, print in one line "The network is connected." if there is a path between any pair of computers; or "There are k components." where k is the number of connected components in this network.

Sample Input 1:

5
C 3 2
I 3 2
C 1 5
I 4 5
I 2 4
C 3 5
S

Sample Output 1:

no
no
yes
There are 2 components.

Sample Input 2:

5
C 3 2
I 3 2
C 1 5
I 4 5
I 2 4
C 3 5
I 1 3
C 1 5
S

Sample Output 2:

no
no
yes
yes
The network is connected.

代码如下:
#include <cstdio>
#include <cstring>

#define N 10010

/*查找函数: 通过节点的值查找其所在树的根节点*/
int Find(int father[], int n, int x);
/*合并函数: 合并两个节点*/
void Union(int father[], int n, int a, int b);

int main()
{
    char operation[10];
    int father[N] = {0};      //初始化,所有节点都是根节点,深度为0
    int n;
    int a, b;

    scanf("%d", &n);
    while (scanf("%s", operation) == 1)
    {
        if (strcmp(operation, "S") == 0) break;
        if (strcmp(operation, "C") == 0)
        {
            scanf("%d%d", &a, &b);
            if (Find(father, n, a) == Find(father, n, b)) //若根节点相同则在同一颗树上
                printf("yes\n");
            else
                printf("no\n");
        }
        else
        {
            scanf("%d%d", &a, &b);
            Union(father, n, a, b);
        }
    }

    int k = 0;      //统计根节点的个数(树的个数)
    for (int i = 1; i <= n; i++)
    {
        if (father[i] <= 0)
            k++;
    }
    if (k == 1)     //只有一棵树,即全部连通
        printf("The network is connected.\n");
    else
        printf("There are %d components.\n", k);

    return 0;
}

void Union(int father[], int n, int a, int b)
{
    int ARoot, BRoot;
    ARoot = Find(father, n, a);    //找到a节点所在树的根节点
    BRoot = Find(father, n, b);    //找到b节点所在树的根节点
    if (ARoot == BRoot)
        return;
    else if (father[ARoot] > father[BRoot]) //B树深度大于A树
    {
        /*树的深度不变*/
        father[ARoot] = BRoot;              //将A树指向B树
    }
    else
    {
        if (father[ARoot] == father[BRoot]) //两树深度相等
            father[ARoot]--;    //树的深度加1,根节点对应数组的值(非正数)的绝对值为这棵树的深度
        father[BRoot] = ARoot;
    }
}

int Find(int father[], int n, int x)
{
    int root;
    for (root = x; father[root] > 0; root = father[root]);  //向上遍历直到根节点退出循环
    return root;    //返回根节点
}

图解样例2如下:

				
时间: 2024-12-31 14:51:09

04-树5. File Transfer--并查集的相关文章

数据结构之 --- 树的应用(并查集)

概念: 并查集是一种简单的集合表示,它支持一下三种操作: 1)Union(S, Root1, Root2): 把集合S中的子集合Root2并入集合Root1中.要求Root1和Root2互不相交,否则不执行合并. 2)Find(S, x): 查找集合S中单元素x所在的子集合,并返回该子集合的名字. 3)Inital(S):将集合S中的每一个元素都初始为只有一个单元的子集合. 通常用树(森林)的双亲表示作为并查集的存储结构,每个子集合以一棵树表示.所有表示子集合的树构成森林, 存放在双亲表示数组内

51nod1307(暴力树剖/二分&amp;dfs/并查集)

题目链接: http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1307 题意: 中文题诶~ 思路: 解法1:暴力树剖 用一个数组 num[i] 维护编号为 i 的边当前最大能承受的重量. 在加边的过程中根据给出的父亲节点将当前边所在的链上所有边的num都减去当前加的边的重量, 注意当前边也要减自重. 那么当num首次出现负数时加的边号即位答案: 事实上这个算法的时间复杂度是O(n^2)的, 不过本题并没有出那种退化成单链的

Codeforces 1140F Extending Set of Points 线段树 + 按秩合并并查集 (看题解)

Extending Set of Points 我们能发现, 如果把x轴y轴看成点, 那么答案就是在各个连通块里面的x轴的个数乘以y轴的个数之和. 然后就变成了一个并查集的问题, 但是这个题目里面有撤销的操作, 所以我们要把加入和撤销操作变成 这个点影响(L , R)之间的询问, 然后把它丢到线段树里面分成log段, 然后我们dfs一遍线段树, 用按秩合并并查集取维护, 回溯的时候将并查集撤销. #include<bits/stdc++.h> #define LL long long #def

NYOJ129 树的判定 【并查集】

树的判定 时间限制:1000 ms  |  内存限制:65535 KB 难度:4 描述 A tree is a well-known data structure that is either empty (null, void, nothing) or is a set of one or more nodes connected by directed edges between nodes satisfying the following properties. There is exac

bzoj3694: 最短路(树链剖分/并查集)

bzoj1576的帮我们跑好最短路版本23333(双倍经验!嘿嘿嘿 这题可以用树链剖分或并查集写.树链剖分非常显然,并查集的写法比较妙,涨了个姿势,原来并查集的路径压缩还能这么用... 首先对于不在最短路径树上的边x->y,设t为最短路径树上lca(x,y),则t到y上的路径上的点i到根的距离都可以用h[x]+dis[x][y]+h[y]-h[i](h[]为深度)来更新,因为h[i]一定,只要让h[x]+dis[x][y]+h[y]最小就行,这里用树剖直接修改整条链上的数,就可以过了. 并查集的

PAT甲级——1118 Birds in Forest (并查集)

此文章 同步发布在CSDN:https://blog.csdn.net/weixin_44385565/article/details/89819984 1118 Birds in Forest (25 分) Some scientists took pictures of thousands of birds in a forest. Assume that all the birds appear in the same picture belong to the same tree. Yo

bzoj3674: 可持久化并查集

用可持久化线段树维护可持久化并查集. 调了一下午,改为按秩合并就过了... #include<bits/stdc++.h> #define REP(i,a,b) for(int i=a;i<=b;i++) #define MS0(a) memset(a,0,sizeof(a)) using namespace std; typedef long long ll; const int maxn=200100; const int INF=1e9+10; int n,m; int fa[ma

poj1308 并查集

题意:和hdu1272差不多,只不过给出的是有向图,问图中的点是否是一颗树. 还是用并查集合并点,对于一条边,如果连接的两点已经在同一并查集内,则可以直接判否.合并时按边的方向记录点的入度,如果某个点入度大于1也就是某个点有多个父亲节点,则说明不是树.合并时顺便记录合并总次数,最后合并 点数-1 次则是树. 1 #include<stdio.h> 2 #include<string.h> 3 4 int fa[100005],num[100005]; 5 bool vi[10000

snnu(1110) 传输网络 (并查集+路径压缩+离线操作)

1110: 传输网络 Time Limit: 3 Sec  Memory Limit: 512 MBSubmit: 43  Solved: 18[Submit][Status][Web Board] [Edit] Description Byteland国家的网络单向传输系统可以被看成是以首都Bytetown为中心的有向树,一开始只有Bytetown建有基站,所有其他城市的信号都是从Bytetown传输过来的.现在他们开始在其他城市陆续建立了新的基站,命令“C x“代表在城市x建立了一个新的基站

并查集及应用

在信息学竞赛中,并查集是一种不可忽视的一部分内容,把最近几年的NOI和NOIP复赛题目大致浏览了一遍,发现有好几道应用并查集的题目,因此本文由浅入深的介绍并查集在编程中的巧妙应用. 什么是并查集?并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题.常常在使用中以森林来表示.集就是让每个元素构成一个单元素的集合,并就是按一定顺序将属于同一组的元素所在的集合合并. 并查集的主要操作: 1.初始化:把每个点所在集合初始化为其自身: 2.查找:查找元素所在的