并查集
三:并查集
背景
在一些有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 }
运行截图
个人总结
并查集核心代码包含两个部分,一是查找,二是合并,有时候为了优化查找,降低时间复杂度,还可以在查找的时候进行路径压缩。把与根节点间接连接的子节点直接连到根节点上,这样就降低了树的高度,在以后的查找中,可以直接找到根节点,而不用进行回溯。