集合的表示
![](https://img-blog.csdnimg.cn/e56ae7978f3045b0b4e56a7591acaa50.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5oyg5aS05bCP6Lev6aOe,size_20,color_FFFFFF,t_70,g_se,x_16)
![](https://img-blog.csdnimg.cn/991b55a9b1bf438a8dcb9a8075242518.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5oyg5aS05bCP6Lev6aOe,size_20,color_FFFFFF,t_70,g_se,x_16)
![](https://img-blog.csdnimg.cn/6d40fb4ec27d40d09f6aae3a7acc5acb.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5oyg5aS05bCP6Lev6aOe,size_20,color_FFFFFF,t_70,g_se,x_16)
我们可以用一个树来表示一个集合,一棵树是一个集合,如果是两个集合就是两棵树,那么树里的每个节点呢是代表集合中的元素,用树根来代表这个集合,所以想查某个元素属于哪个集合,你只要在这个树上面去找它的根结点是谁,如果要把两个集合并在一起,那么就是把两个树并在一起,形成一个更大的树。
在这里面,我们很重要的一个操作是要查某个元素属于哪个集合,是从这个元素的结点开始去找这个树的树根,也就是说去往上找,我们]要把树并在一起呢,只要把一个根连到另外一个根就可以了。所以在这两个并查操作过程当中,我们不存在己知一个结点找它儿子是谁,而我们更重要的是要知道已知1个结点它父亲是谁。因为根据这个我们可以很快找到根了,所以呢这是一种树的一种表示方法。
双亲表示法:每个结点的指针呢都指向它的父亲。
对于这种存储方式,我们可以用链表进行存储,但是更好的一种方式是使用数组进行存储。
![](https://img-blog.csdnimg.cn/fb2f60a066bc43faac62eb3f922cc1bc.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5oyg5aS05bCP6Lev6aOe,size_20,color_FFFFFF,t_70,g_se,x_16)
Data代表结点的信息
Parent代表下标,指向父亲的位置。
根结点没有父亲结点,所以将根结点的Parent设为-1.
利用这种结构,我们很容易就知道根结点是谁。
集合运算
查找
![](https://img-blog.csdnimg.cn/0bb3d57fc21c4475ba0576087b899d59.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5oyg5aS05bCP6Lev6aOe,size_20,color_FFFFFF,t_70,g_se,x_16)
![](https://img-blog.csdnimg.cn/5143e732c24c4d7795e8acdc5ce7ebfc.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5oyg5aS05bCP6Lev6aOe,size_20,color_FFFFFF,t_70,g_se,x_16)
int Find( SetType S[ ], ElementType X )
{ /* 在数组S中查找值为X的元素所属的集合 */
/* MaxSize是全局变量,为数组S的最大长度 */
int i;
for ( i=0; i < MaxSize && S[i].Data != X; i++) ;
if( i >= MaxSize ) return -1; /* 未找到X,返回-1 */
for( ; S[i].Parent >= 0; i = S[i].Parent ) ;
return i; /* 找到X所属集合,返回树根结点在数组S中的下标 */ }
并运算
![](https://img-blog.csdnimg.cn/a571166c2fc941d3bbf56e05a2b1a134.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5oyg5aS05bCP6Lev6aOe,size_20,color_FFFFFF,t_70,g_se,x_16)
Root1、Root2分别表示元素x1与元素x2所在的下标。
通过if语句判断Root1与Root2是否是同一个树根,不是同一个树根才做Union合并操作。
void Union( SetType S[ ], ElementType X1, ElementType X2 ) {
int Root1, Root2;
Root1 = Find(S, X1);
Root2 = Find(S, X2);
if( Root1 != Root2 )S[Root2].Parent = Root1; }
![](https://img-blog.csdnimg.cn/639191b4d0564f46a8cda62d5c956106.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5oyg5aS05bCP6Lev6aOe,size_20,color_FFFFFF,t_70,g_se,x_16)
合并操作,将元素3的父亲结点的下标指向元素1,完成合并。
如果不断地做Union的时候呢,这个树会变得越来越大。而且还有另外一种可能,就是这个树呢越来越高,导致我们的查找Find效率会比较差,所以尽量把小的集合并到大的集合中去。我们要知道哪个集合大,所以我们要知道根结点这棵树下一共有多少个元素,所以就将根结点的Parent的下标改为元素的个数,前面再加一个负号即可。
![](https://img-blog.csdnimg.cn/0d1b885b371a4fcf8f3b9dce720930b3.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5oyg5aS05bCP6Lev6aOe,size_20,color_FFFFFF,t_70,g_se,x_16)
代码
代码:
#define MAXN 1000 /* 集合最大元素个数 */
typedef int ElementType; /* 默认元素可以用非负整数表示 */
typedef int SetName; /* 默认用根结点的下标作为集合名称 */
typedef ElementType SetType[MAXN]; /* 假设集合元素下标从0开始 */
void Union( SetType S, SetName Root1, SetName Root2 )
{ /* 这里默认Root1和Root2是不同集合的根结点 */
/* 保证小集合并入大集合 */
if ( S[Root2] < S[Root1] ) { /* 如果集合2比较大 */
S[Root2] += S[Root1]; /* 集合1并入集合2 */
S[Root1] = Root2;
}
else { /* 如果集合1比较大 */
S[Root1] += S[Root2]; /* 集合2并入集合1 */
S[Root2] = Root1;
}
}
SetName Find( SetType S, ElementType X )
{ /* 默认集合元素全部初始化为-1 */
if ( S[X] < 0 ) /* 找到集合的根 */
return X;
else
return S[X] = Find( S, S[X] ); /* 路径压缩 */
}