在一些应用的问题中,需将n个不同的元素划分成一组不相交的集合。开始时,每个元素自成一格单元素集合,然后按一定顺序将属于同一组的元素的集合合并。其间要反复用到查询某个元素属于哪个集合的运算。适合于描述这类问题的抽象数据类型称为并查集。
1. 并查集的概述
并查集的数学模型是一组不相交的动态集合的集合S={A,B,C,...},它支持以下的运算:
(1)union(A,B):将集合A和B合并,其结果取名为A或B;
(2)find(x):找出包含元素x的集合,并返回该集合的名字。
在并查集中需要两个类型的参数:集合名字的类型和元素的类型。在许多情况下,可以用整数作为集合的名字。如果集合中共有n个元素,可以用范围【1:n】以内的整数表示元素。实现并查集的一个简单方法是使用数组表示元素及其所属子集的关系。其中,用数组下标表示元素,用数组单元记录该元素所属的子集名字。如果元素类型不是整型,则可以先构造一个映射,将每个元素映射成一个整数。这种映射可以用散列表或其他方式实现。
2. 并查集的实现
采用数结构实现并查集的基本思想是,每个集合用一棵树表示。树的结点用于存储集合中的元素名。每个树结点还存放一个指向其父结点的指针。数根结点处的元素代表该数所表示的集合。利用映射可以找到集合中所对应的数结点。
父亲数组是实现上述数结构的有效方法。
Java实现代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public class UnionFind { Node[] node; //父亲数组 //并查集中的结点 private static class Node{ int parent; boolean root; private Node(){ parent = 1 ; root = true ; } } //将每个元素初始化为一颗单结点树 public UnionFind( int n){ node = new Node[n + 1 ]; for ( int e= 0 ; e <= n; e++){ node[e] = new Node(); } } } |
find运算就是从元素e相应的结点走到树根处,找出所在集合的名字。
1
2
3
4
5
6
|
public int find( int e){ while (!node[e].root){ e = node[e].parent; } return e; } |
union运算,合并两个集合,只要将表示其中一个集合的树的数根改为表示另一个集合的树的数根的儿子结点。
1
2
3
4
5
|
public void union( int a, int b){ node[a].parent += node[b].parent; node[b].root = false ; node[b].parent = a; } |
3. 快速并查集的实现
容易看出,在最坏情况下,合并可能使n个结点的树退化成一条链,导致对所有元素各执行一次find将耗时O(n^2)。可做出以下改进来加速find运算。
1.)小树合并到大树
在union操作中,将表示小树的数根改为表示大树的数根的儿子结点。于是并查集中每个结点至多被移动O(logn)次,从而每个结点到数根的距离不会超过O(logn)。所有,每次find运算只需O(logn)时间。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
/* * 合并两个集合(加速) * 将表示小树的数根改为表示大树的数根的儿子结点 */ public void union( int a, int b){ if (node[a].parent < node[b].parent){ node[b].parent += node[a].parent; node[a].root = false ; node[a].parent = a; } else { node[a].parent += node[b].parent; node[b].root = false ; node[b].parent = a; } } |
2.)路径压缩技术
在执行find时,实际上找到了从一个结点到数根的一条路径。路径压缩就是把这条路上所有的结点都提升1层,以加快找到根结点的时间。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
/* * find运算(加速) * 从元素e相应的结点走到树根处,找出所在集合的名字 */ public int find(int e){ int current = e, p ,gp; /*排除当前结点或其父结点为根的情况后,加速find*/ if (node[current].root){ return current; } p = node[current].parent; if (node[current].root){ return p; } gp = node[current].parent; while ( true ){ node[current].parent = gp; if (node[gp].root){ return gp; } current = p; p = gp; gp = node[p].parent; } } |
(并查集实现的详细代码及更多相关数据结构的代码均在git)
参考资料:
1.《算法设计与分析》
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/why_still_confused/article/details/51497973