并查集

三:并查集

背景

在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。

定义

并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。

集就是让每个元素构成一个单元素的集合,也就是按一定顺序将属于同一组的元素所在的集合合并。

主要操作

初始化

把每个点所在集合初始化为其自身。

通常来说,这个步骤在每次使用该数据结构时只需要执行一次,无论何种实现方式,时间复杂度均为O(N)。

查找

查找元素所在的集合,即根节点。

合并

将两个元素所在的集合合并为一个集合。

通常来说,合并之前,应先判断两个元素是否属于同一集合,这可用上面的“查找”操作实现。

问题描述

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

基本要求

(1)       输入n,m,分别表示n个城市和m条路;

(2)       接下来m行,每行两个数据a,b,表示a,b城市之间有道路相连;

(3)       输出一个整数,最少还需要几条路。

测试数据

       输入

              4 2

              1 3

              4 3

             

       输出

              1

算法思想

       此题求最少需要几条路,如果输入的数据所有城市都直接或间接连接在一起,那么最少需要0条路;或者不是都连接在一起,这时就要统计含有几棵树,换句话说,统计含有几个根节点,假设为ans个,那么需要的最少路为ans-1。所以问题就转换为统计所输入的数据分别独立存在于几个集合中,也就是根节点的个数。

       首先定义一个数组father[],初始令father[i]=i,这时每个节点都是自己的根节点。

每输入一条路,就对两个节点合并,这样一个集合中的节点就都属于一个根节点。

最后对所有节点查询,如果father[i]=i,即说明找到了一个根节点,统计即可。

实现过程

 …………

实现过程

代码实现

 1 #include<stdio.h>
 2 
 3 #include<iostream>
 4 
 5 #include<algorithm>
 6 
 7 using namespace std;
 8 
 9 int fa[10100];
10 
11 int findroot(int x)
12 
13 {
14 
15     int r=x;
16 
17     while(r!=fa[r])
18 
19     r=fa[r]; //查找父节点
20 
21     int i=x,j;
22 
23     while(i!=r)
24 
25     {
26 
27         j=fa[i];
28 
29         fa[i]=r;
30 
31         i=j;
32 
33     }//路径压缩
34 
35     return r;
36 
37 }
38 
39  
40 
41 void Union(int x,int y) {
42 
43   int nx=findroot(x);
44 
45   int ny=findroot(y);
46 
47   if(nx!=ny)
48 
49   {
50 
51     fa[ny]=nx;
52 
53   }//合并
54 
55 }
56 
57  
58 
59 int main()
60 
61 {
62 
63     int n,m;
64 
65     while(scanf("%d",&n)!=EOF,n)
66 
67     {
68 
69         scanf("%d",&m);
70 
71         for(int i=1;i<=n;i++) fa[i]=i;
72 
73         for(int i=0;i<m;i++)
74 
75         {
76 
77             int a,b;scanf("%d%d",&a,&b);
78 
79             Union(a,b);
80 
81         }
82 
83         int cnt=0;
84 
85         for(int i=1;i<=n;i++)
86 
87             if(fa[i]==i)
88 
89             cnt++;
90 
91         printf("%d\n",cnt-1);
92 
93     }
94 
95     return 0;
96 
97 }

 

运行截图

 

 

个人总结

并查集核心代码包含两个部分,一是查找,二是合并,有时候为了优化查找,降低时间复杂度,还可以在查找的时候进行路径压缩。把与根节点间接连接的子节点直接连到根节点上,这样就降低了树的高度,在以后的查找中,可以直接找到根节点,而不用进行回溯。

 

posted on 2016-08-24 19:23  左岸zero  阅读(165)  评论(0编辑  收藏  举报

导航