赞
踩
目录
并查集的逻辑结构是一个包含N个元素的集合,如图:
我们将各个元素划分为若干个互不相交的子集,如图:
我们假设第一个集合中的元素为:苹果、橘子、香蕉等各种水果,结点10就表示水果1。
我们假设第二个集合中的元素为:油菜、香菜,芹菜等各种蔬菜,结点11就表示蔬菜。
我们假设第三个集合中的元素为:高数、线代、计网等各种学科,结点9就表示学科。
我们假设第四个集合中的元素为:蓝莓、草莓、杨梅等各种水果,结点14就表示水果2。
(注:四个集合互不相交)
下面有几个问题:
在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,只能用并查集来描述。
并查集的存储结构可以通过 树的双亲表示法 来理解。
我们先来看一下树的双亲表示法:
这种存储方式采用一组连续空间来存储每个结点,同时在每个结点中增设一个伪指针,指示其双亲结点在数组中的位置。如图所示,根结点下标为0,其伪指针域为-1。 该存储结构利用了每个结点(根结点除外)只有唯一双亲的性质,可以很快得到每个结点的双亲结点,但求结点的孩子时需要遍历整个结构。 |
接下来,我们来看并查集:
若设有一个全集合为 S ={0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},初始化时每个元素自成一个单元素子集合,每个子集合的数组值为 -1,如图:
经过一段时间的计算,这些子集合合并为3个更大的子集合,并查集的树形表示和存储结构如图:
可见,并查集可用数组进行表示,数组下标序号表示该数据元素,数组值存放其双亲信息。
分析: 对于上一个图 结点 0 的数组值为 2 ,代表双亲是 2 ; 结点 2 的数组值为 7 ,代表双亲是 7 ; 结点 7 的数组值为 9 ,代表双亲是 9 ; 结点 14 的数组值为 -1 ,代表该结点为根结点 ; 至此,我们仅通过数组这一数据结构便完成了结点信息的存放和结点双亲的查找。 |
下面,我们用代码描述并查集:
- #define SIZE 100
- int UFSets[SIZE]; //集合元素数组
- void Initial(int S[])
- {
- for (int i = 0; i < SIZE; i++)
- S[i] = -1;
- }
- int Find(int S[], int x)
- {
- while (S[x] >= 0) //循环寻找 x 的根
- x = S[x];
- return x; //根的 S[] 小于0
- }
- void Union(int S[], int Root1, int Root2)
- {
- //要求Root1与Root2是不同的,且表示子集合的名字
- if (Root1 == Root2) return;
- S[Root2] = Root1;
- }
(Root2的双亲指向了Root1,意味着Root2带领的整个集合都指向了双亲Root1,实现了合并)
- void Union(int S[], int Root1, int Root2)
- {
- //要求Root1与Root2是不同的,且表示子集合的名字
- if (Root1 == Root2) return;
- if (S[Root2] > S[Root1]) //Root2结点数更少
- {
- S[Root1] += S[Root2];
- S[Root2] = Root1; //小树合并到大树,即Root2指向Root1
- }
- else
- {
- S[Root2] += S[Root1];
- S[Root1] = Root2;
- }
- }
- int Find(int S[], int x)
- {
- int root = x;
- while (S[root] >= 0) root = S[root]; //循环找到根
- while (x != root) //x不为根结点,则压缩路径
- {
- int t = S[x]; //t指向x的父节点
- S[x] = root; //x直接挂到根结点下
- x = t;
- }
- return root; //返回根节点编号
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。