数据结构与C语言实现(七)——树(下):集合与集合例题

本文介绍了一种使用树形结构实现集合的操作方法,并详细解释了如何通过并查集算法进行查找和合并操作,包括路径压缩和按秩归并等优化手段。此外,还提供了一个具体的文件传输示例程序,展示了如何利用并查集解决实际问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先是集合的基本数据类型,这里的查找我使用了规模对比的优化方式。

//用树实现集合的表现形式,注意是树不是二叉树,这是一种包含指向爸爸下标的数据结构
#include <stdio.h>
#include <stdlib.h>
#define MaxSize 100
typedef struct
{
	int data;
	int parent;//负数表示为集合首个元素(即根节点),绝对值为数量
}SetType;

SetType S[10];//假设有10个元素
//查找某个元素所在的集合

int Find(SetType S[], int X)
{
	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);//不断地向上找爸爸,直到发现爸爸值为-1
	return i;//找到X所属的集合,返回树根节点的数组S中的下标
}

//用for循环真的反人类,不如while舒服,这里用while写一下
int FindWhile(int item,SetType S[])
{
	int i;
	while (S[i].data != item) i++;//先找到i
	while (S[i].parent >= 0) i = S[i].parent;//向上找爸爸,直到爸爸小于0
	return i;//集合首元素
}

//集合的并运算
/*
1.找到元素所在的根节点
2.然后将一个树的parent指向另一个树即可
*/

void Union(SetType S[], int X1, int X2)
{
	int Root1, Root2;
	Root1 = FindWhile(X1, &S[MaxSize]);
	//但是擅长指针的你,为什么不写成下面这种形式呢
	Root2 = FindWhile(X2, S);
	if ((Root1 != Root2) && (-Root1 > -Root2))//root1的数量比root2的数量多
	{
		Root1 = Root1 + Root2;//将头合并为两个负数的和
		S[Root2].parent = Root1;
		
	}
	else
	{
		Root2 = Root1 + Root2;//将头合并为两个负数的和,注意我这里用的是按照规模归并
		S[Root1].parent = Root2;
		
	}		
	将一个集合的头指向另一个集合的父亲,那么现在roo1就是并集之后的集合首元素	
}
/*
为了查找的方便,我们希望能够把小的,元素少的集合并到大的,元素多的集合。
那么,我们可以不把parent统一规定为-1.可以规定为数量的负数。

*/

下面是一道堆的例题,采用了一种新的数据结构形式。

/*
集合的简化表示方法
1.我们将数量可数的元素映射到0到N-1
2.然后建立数组A[N],下标表示的是元素的data,数组中存放的内容是parent的值
*/

#include <stdio.h>
#include <stdlib.h>
#define MaxSize 1000

typedef int ElementType;
typedef int SetName;
typedef ElementType SetType[MaxSize];
//比如说 int S[100] 可以用 SetType S

SetName Find(SetType S, ElementType X)
{
	while (S[X] > 0)//父节点大于0,代表不是集合首元素
	{
		X = S[X];//S[X]代表的是父节点,赋值给X那么再S[X],就相当于是父亲的父亲
	}
	return X;//直到找到S[X]<0的,那么就返回数据X,X的值就是集合首元素的值
}

//路径压缩下的Find
SetName FindRar(SetType S, ElementType X)
{
	if (S[X] < 0) return X;
	else
		return S[X] = Find(S, S[X]);
	//这句话做了三件事,1.先找到根2.把根变成X的父节点3.再返回根
}


//按秩归并
//一多一少,高度不变,同样高度的归并,高度++;
void Union(SetType S, SetName Root1, SetName Root2)
{
	//显然Root1代表的是S下标,同时表示数据
	//两个集合的并只需要把一个的S[X]指向另一个
	if(-S[Root2] > -S[Root1])//如果树2比树1高,那么树二就是合并后的树
		S[Root1] = Root2;
	else
	{
		if (S[Root1] == S[Root2])
			S[Root1]--;
		S[Root2] = Root1;
	}
	//S[Root2]指的是Root2这个值的爸爸,原来是-1,后来变成了Root1
}

void Init(SetType S, ElementType num)
{
	for (int i = 0; i < num; i++)
		S[i] = -1;
}
/*
题目:File Transfer
5		五台计算机
C 3 2   3和2之间能联通吗?
I 3 2   将3和2连接在一起
C 1 5	1和5能联通吗? 
I 4 5
I 2 4
C 3 5
S       退出
然后输出系统中有几个联通集
*/

void Input_connection(SetType S)
{
	ElementType com1, com2;
	scanf("%d %d", &com1, &com2);
	int Root1 = Find(S, com1 - 1);
	int Root2 = Find(S, com2 - 1);
	if(Root1 != Root2)
		Union(S, Root1, Root2);
	printf("%d 和 %d  现在连接在了一起\n ",com1,com2);
}

void Check_connection(SetType S)
{
	ElementType com1, com2;
	scanf("%d %d", &com1, &com2);
	if (Find(S, com1 - 1) == Find(S, com2 - 1))
		printf("能够连通\n");
	else
		printf("不能够连通\n");
}

void Check_networks(SetType S,int com_num)
{
	int num = 0;
	for (int i = 0; i < com_num; i++)
	{
		if (S[i] < 0) num++;
	}
	printf("目前有 %d 个连通区域\n",num);
}
int main()
{
	SetType S;
	int computer_num = 0;
	char in = 0;
	printf("请告诉我一共有多少台电脑\n");
	scanf("%d", &computer_num);
	Init(S, computer_num);
	do
	{
		scanf("%c", &in);
		switch (in)
		{
			case 'I':Input_connection(S); break;
			case 'C':Check_connection(S); break;
			case 'S':Check_networks(S,computer_num); break;
			default:break;
		}

	} while (in != 'S');//只要输入的字符不等于S,那么就这么循环
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值