集合运算例题

File Transfer

0x00 集合的简化表示

QQ截图20200206154509

​ 在之前 我们用一个自定义结构数组来储存一个集合树,所以我们在一个元素的根节点的时候,必然先要便利数组,先找到这个元素,再找其根节点,那么对于n个节点,其时间复杂度是O(N^2),所以必须改进。

​ 改进的办法就是,我们不开辟空间来储存元素,而是用数组下标来表示或者说映射元素,那么,每次需要找到某个节点,只需要提供对应的i就是了。在本题中,我们的元素事先被编号了,那么编号-1,即是其下标,下标+1,即是对应元素。在找到元素这一项任务上n个元素时间复杂度是O(N)。

QQ截图20200206155948

0x01 题目描述

QQ截图20200206155244 第一行代表元素个数

​ 后面的每一行, 第一个是指令 后面两个 是元素 C 是check 查询两个主机是否相连,也就是是否在同一个集合,I是input_connection 将两个主机连在一起,也就是合并集合(如果不是在一个集合的话),S则是结束。

​ 要求在每条C指令后面输出是否连通,S后署出一个有几个连通分量。

0x02 代码实现

#include <stdio.h>
#include <stdlib.h>
int find(int *data,int x){
	int ret=x;
	while(data[ret]>0){
		ret=data[ret];
	}
	return ret;
}
void un(int* data,int a,int b){
	int root1=find(data,a);
	int root2=find(data,b);
	if(root1!=root2){
		data[root2]=root1;
	}
} 
int main(){
	int m;
	int i;
	int a,b;
	scanf("%d",&m);
	getchar();
	int* data=(int*)malloc(sizeof(int)*m);
	for(i=0;i<m;i++){
		data[i]=-1;
	}
	char C;
	do{
		scanf("%c",&C);
		switch (C){
			case 'C':{
				scanf("%d %d",&a,&b);
				getchar();
				if(find(data,a-1)==find(data,b-1)) printf("yes\n");
				else printf("no\n");
				break;
			}
			case 'I':{
				scanf("%d %d",&a,&b);
				getchar();
				un(data,a-1,b-1);
				break;
			}
			case 'S':{
				int cnt=0;
				for(i=0;i<m;i++){
					if(data[i]<0){
						cnt++;
					}
				}
				printf("there are %d components.",cnt);
				break;
			}
					
				 
		}
	}while(C!='S');
	
	return 0;
} 

以上代码测试通过

0x03 性能优化

在上面的代码 un函数(并集函数)中,总是简单的将root2的父节点设置位root1,这样做有个问题,就是树的高度会不断增加,有可能使find 变成O(n^2)。 所以我们得引入按秩归并

QQ截图20200206170444

QQ截图20200206170539

QQ截图20200206170605

​ 另外一个优化的方式就是,在find函数中 我们把我们要找的元素的根节点找到后,直接设置为该元素的父节点,这种优化方式称为路径压缩

​ 下图的路径压缩是尾递归方式实现的,其实可以用循环。

QQ截图20200206170959