YAZONG 我的开源

(数据结构)第五讲-树(5.3 集合及运算)

  , ,
0 评论0 浏览

5.3.1 集合的表示

主要运算:两个集合合并与查某个元素属于某个集合。

image.png

如果A和B连接在一起,B和C连接在一起,那么A和C就连在一起,这种连接要传递性。

image.png

两个电脑连在一起,就把这俩集合合并。

是否联通,要确认两个电脑是否属于统一集合,相同集合就相连,否则不相连。

这俩问题相当于如何管理一堆集合?

一个把两个集合合并在一起,一个查某个元素属于哪个集合,这种问题叫做并查集。

要么要思考,如何存储一个集合?

俩集合合并到一起,相当于两棵树合并到一起,形成一颗更大的树。

image.png

查某个元素属于某个集合,是直接去找树根。

两棵树合并,只要把一个根接到另外一根即可。

在并查操作过程中,不存在已知一个结点找儿子是谁,而是找父亲是谁,这样就能很快的找到根,这是树的一种表示方法,这叫双亲表示法,这里,每个结点的指针都指向父亲。

而二叉树,一个结点指向儿子,一个结点指向父亲。

这种双亲表示法的树结构如何存储?

image.png

/*
集合(课程提供讲解版)
*/
/*
集合的数组表示
*/
typedef struct{
	/*
	元素内容
	*/
	ElementType Data;
	/*
	负数表示根结点,非负数表示双亲节点的下标.
	*/
	int Parent;
} SetType;

数组中的每一行是分量。

分量下标:每个元素的下标值,标记每个元素所在数组中的位置。

分量data:每个元素的基本信息,比如1-10。

分量Parent:指向每个元素父结点所在数组下标的位置,没有父亲用-1表示。

5.3.2 集合的操作(file transfer优化与对比)

查询元素所在集合(根结点表示)

image.png

/*
查找某个元素所在的集合(用根结点表示一个集合)
在数组S中查找值为X的元素所属的集合
*/
int Find(SetType S[], ElementType X){
	int i;
	/*
	从数组的头上一个个往后找
	当Data == X时退出集合
	*/
	for(i = 0; i < MaxSize && S[i].Data != X; i++);
	/*
	未找到X,返回-1.
	如果x < MaxSize,证明X在0-MaxSize之间.
	*/
	if( x >= MaxSize){
		return -1;
	}
	/*
	找到X所属集合,返回树根结点在数组S中的下标.
	当i = S[i].Parent,那么Parent>0就一直往上走.
	当Parent=-1,就是根结点.
	*/
	for( ; S[i].Parent >= 0; i = S[i].Parent);
	/*
	退出时,i=树的根的下标.
	*/
	return i;
}

集合的并运算(小合并大)

image.png

/*
集合的并运算
分别找到X1和X2两个元素所在集合树的根节点.
如果它们不同根,则将其中一个根结点的父结点指针设置成另一个根结点的数组下标.
*/
void Union(SetType S[], ElementType X1, ElementType X2){
	int Root1, Root2;
	Root1 = Find(S, X1);
	Root2 = Find(S, X2);
	/*
	当X1和X2不属于同一个子集时,才需要合并.
	*/
	if( Root1 != Root2){
		S[Root2].Parent = Root1;
	}
}

image.png

把元素2和元素5合并在一起,那么通过2找到1,通过5找到3,那么把Root2挂到Root1中。

image.png

这样不断Union时,树会越来越大,越来越高,导致Find从根节点开始往上一层层找时,效率越来越差。

这样,为了保证集合后续的查询效率,可以采用小的集合合并到相对大的集合中去。

当树越来越庞大时,需要知道每个集合有多少元素,只需要根节点记录,其他结点不需要记录,如果在数组中增加一个分量,那么增加的空间是不需要的。

那么可以使用Parent,负数代表根结点,而这个树的绝对值代表集合的元素个数,就可以推出每个集合的大小了,那么就需要修改Union函数,来判别集合大小并设置大集合与小集合的挂载方式。

源码

/*
集合的定义与并查操作(课程提供最终版)
*/
#define MAXN 1000/* 集合最大元素个数 */
typedef int ElementType;/* 默认元素可以用非负整数表示 */
typedef int SetName;/* 默认用根结点的下标作为集合名称 */
typedef ElementType SetType[MAXN];/* 假设集合元素下标从0开始 */

/*
查找某个元素所在的集合
*/
SetName Find(SetType S,ElementType X){
	/* 默认集合元素全部初始化为-1 */
	if(S[X] < 0){
		/* 找到集合的根 */
		return X;
	}
	else{
		/* 路径压缩 */
		return S[X] = Find(S, S[X]);
	}
}

/*
集合的并运算
这里默认Root1和Root2是不同集合的根结点.
保证小集合并入大集合.
*/
void Union(SetType S, SetName Root1, SetName Root2){
	if(S[Root2] < S[Root1]){
		/* 如果集合2比较大 */
		/* 集合1并入集合2  */
		S[Root2] += S[Root1];
		S[Root1] = Root2;
	}
	else if(S[Root2] > S[Root1]){
		/* 如果集合1比较大 */
		/* 集合2并入集合1  */
		S[Root1] += S[Root2];
		S[Root2] = Root1;
	}
}

标题:(数据结构)第五讲-树(5.3 集合及运算)
作者:yazong
地址:https://blog.llyweb.com/articles/2023/10/22/1697920942003.html