数据结构基础:P5.3-树(三)--->集合及运算

本系列文章为浙江大学陈越、何钦铭数据结构学习笔记,前面的系列文章链接如下
数据结构基础:P1-基本概念
数据结构基础:P2.1-线性结构—>线性表
数据结构基础:P2.2-线性结构—>堆栈
数据结构基础:P2.3-线性结构—>队列
数据结构基础:P2.4-线性结构—>应用实例:多项式加法运算
数据结构基础:P2.5-线性结构—>应用实例:多项式乘法与加法运算-C实现
数据结构基础:P3.1-树(一)—>树与树的表示
数据结构基础:P3.2-树(一)—>二叉树及存储结构
数据结构基础:P3.3-树(一)—>二叉树的遍历
数据结构基础:P3.4-树(一)—>小白专场:树的同构-C语言实现
数据结构基础:P4.1-树(二)—>二叉搜索树
数据结构基础:P4.2-树(二)—>二叉平衡树
数据结构基础:P4.3-树(二)—>小白专场:是否同一棵二叉搜索树-C实现
数据结构基础:P4.4-树(二)—>线性结构之习题选讲:逆转链表
数据结构基础:P5.1-树(三)—>堆
数据结构基础:P5.2-树(三)—>哈夫曼树与哈夫曼编码


一、集合的表示及查找

集合主要的运算有以下四种

1. 求两个集合的交集、并集
2. 求一个集合的补集
3. 求两个集合的差集
4. 给你一个元素,判断它属于哪个集合

1.1 并查集

下面我们来讨论一种很典型的集合运算:并查集

例子:有10台电脑{1,2,3,...,9,10},已知下列电脑之间已经实现了连接:
	 1和2,2和4,3和5,4和7,5和8,6和9,6和10
问: 2和7之间,5和9之间是否是连通的?

解决思路

1、将10台电脑看成10个集合{1},{2},{3},...,{9},{10}
2、已知一种连接“x和y”,就将x和y对应的集合合并
3、查询“x和y是否是连通的“就是判别x和y是否属于同一个集合

这上面所做的主要操作就是下面两个,这样的一种问题就叫并查集。

1、把两个集合并在一起
2、查某个元素属于哪个集合

1.2 并查集的存储

对并查集这样的一个问题,我们首先要考虑一下集合怎么进行表示,也就说怎么存储一个集合。

我们可以用一个树来表示一个集合,一棵树是一个集合。如果是两个集合,那就是两棵树。树里的每个结点代表集合的元素,用树根来代表这个集合。所以想查某个元素属于哪个集合,你只要在这个树上面去找它的根结点是谁。如果要把两个集合并在一起,那么就是把两个树并在一起形成一个更大的树。

比方说我们有三个这样的集合

S1={1,2,4,7}
S2={3,5,8}
S3={6,9,10}

在并查操作过程当中,我们不存在已知一个结点找它儿子是谁。更重要的是要知道已知1个结点,去找它父亲是谁,因为根据这个我们可以很快找到根了。所以这种表示方法就不像我们前面二叉树是一个结点指向儿子,而是有一个结点指向父亲。这种表示方法叫做双亲表示方法,每个结点的指针都指向它的父亲。
在这里插入图片描述


对于上面三棵树,我们可以用链表存储,也可以直接用数组存储。
数组中每个元素的类型描述为:

typedef struct {
	ElementType Data;
	int Parent;
} SetType;

下面就是数组的表示方法。数组的每一个分量是个结构,结构包含Data和Parent。Parent是个位置,指向它父亲在数组中的下标值。比如5的父亲是3,在数组中位于第二个。1、3、6都是根结点,没有父结点,所以其Parent为-1。
在这里插入图片描述


查找操作:给你一个元素X,找它属于哪个集合,对应代码如下:

int Find( SetType S[ ], ElementType X )
{ /* 在数组S中查找值为X的元素所属的集合 */
 /* MaxSize是全局变量,为数组S的最大长度 */
	int i;
	//找X
	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中的下标 */
}

二、集合的并运算

集合的并运算操作步骤如下

1、分别找到X1和X2两个元素所在集合树的根结点
2、如果它们不同根,则将其中一个根结点的父结点指针设置成另一个根结点的数组下标。

对应代码如下:

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;
}

面临的问题:
现在我要将上面例子中的Root2挂在Root1下面,此时Root2的父结点为Root1,Root2(结点4)的Parent也就修改为结点1的下标值0。
在这里插入图片描述
在这样挂的过程中,如果不断地做Union,这个树会变得越来越大。而且还有另外一种可能,就是这个树越来越高。刚才我们的集合1跟集合3都是两层的,并在一起就变成三层了。树越来越高了之后会导致我们的查找操作效率变差。

解决方案:
为了保证集合后面查找的效率,两个集合并在一起的时候,尽量把小的集合并到大的集合里面去,这样有可能高度不会增加,还是大的集合的那个高度。如果按照这个策略要把小的并得大的里面去,那我们一定要知道每个集合到底有多少个元素,怎么样有这样的一个信息?

方法1(造成空间浪费):
每个结点是个结构,包含两个分量:Data和Parent,我们可以增加一个分量用于记录集合的元素个数。但是我们只有根结点才需要记录这个集合有多少个元素,其他的结点都不用记录,那这就导致了空间浪费。
方法2(可行):
各个结点的Parent的值分为两种情况:一种是代表他父结点的下标,是大于等于零的这样的一个值。另外一种是代表根结点,用负数来表示。目前的根结点统一用-1表示,我们可以用另外一个负数来表示根结点,这个负数的绝对值代表这个树的总结点数。比如6那个结点的Parent为-3,代表它是根结点,且这棵树有3个结点。那么1那个结点的Parent为-7,代表它是根结点,且这棵树有7个结点。


三、C语言-集合的定义与并查操作

#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] ); /* 路径压缩 */
}

四、小测验

1、已知a、b两个元素均是所在集合的根结点,且分别位于数组分量3和2位置上,其parent值分别为-3,-2。问:将这两个集合按集合大小合并后,a和b的parent值分别是多少?

A. -5,2
B. -5,3
C. -3,3
D. 2,-2

答案:B

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

知初与修一

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值