朋友圈--并查集应用

版权声明:转载请注明出处,谢谢合作 https://blog.csdn.net/qq_40738840/article/details/85889899

小引

并查集是求解等价关系的得力助手,具体应用如求无向图连通分支数,至少还需几条路才能将一个城市串通迷宫生成,克鲁斯卡尔算法求解最小生成树。它的听起来高大上,实际上却是极为简单的数据结构–森林

并查集顾名思义是并、查集合的操作的实现关键在于一个数组father,该数组下标表示相应的点,值表示该点对应的双亲,初始化全为-1,表示每一个点都构成仅有一个结点且该结点为根的树,每次得到两个点的关系,利用查找函数找到两个点的祖先,并将他们合并构建为一颗二叉树,以此反复,直到所有关系均处理完

最终father状态含义:

  • 负数的个数表示连通分支个数(集合个数)
  • 负数的绝对值表示相应连通分支的节点个数(相应集合的大小)

朋友圈求解

[问题描述]
某学校有N个学生,形成M个俱乐部。每个俱乐部里的学生有着相似的兴趣爱好,形成一个朋友圈。一个学生可以同时属于若干个不同的俱乐部。根据“我的朋友的朋友也是我的朋友”这个推论可以得出,如果A和B是朋友,且B和C是朋友,则A和C也是朋友。请编写程序计算最大朋友圈中有多少人。
[基本要求]
(1)输入说明:输入的第一行包含两个正整数N (N<=30 000)和M (M<=1000),分别代表学校的学生总数和俱乐部的个数。随后的M行每行按以下格式给出一个俱乐部的信息,其中学生从1-N编号:
第i个俱乐部的人数Mi(空格)学生1(空格)学生2… 学生Mi
(2)输出说明:输出一个整数,表示在最大朋友圈中有多少人。
(3)测试用例:
输入 7 4
3 1 2 3
2 1 4
3 5 6 7
1 6
输出 4

数据结构

father数组最终状态:负数的个数表示朋友圈个数;数组值的绝对值表示该朋友圈人数

int father[30005];//下标表示人的标号,值表示双亲节点 

查找

  • 查找并返回当前点所在树的根节点
  • 优化:压缩路径法,即将一棵树中所有的点均直接指向根,可以提高查找效率
//查找当前点所在树的根节点 
int find(int child) 
{
	int f = child;
	while(father[f] > 0){
		f = father[f];
	}
	//优化:压缩路径,即将一棵树中所有的点均直接指向根,可以提高查找效率 
	int j = child;`在这里插入代码片`
	while(j != f){
		father[j] = f;
		j = father[j]; 
	} 
	return f;
}

合并

将两个根合并,等价于将两棵树合并,但二叉树只能有一个根,所以必须抉择,本来选择谁都是可行的,但为了避免树退化,高度过大,每次选择高度小的根接入高度大的根

//合并两个点 
void Union(int fa,int fb)
{
	//优化:为了避免树的退化,每次将高度小的根接到高度大的根 
	if(father[fa] > father[fb]){
		father[fa] += father[fb];//个数相加 
		father[fb] = fa;//fb为fa父亲 
	}
	else{
		father[fb] += father[fa]; 
		father[fa] = fb; 
	}
}

核心处理

从文件读取数据
文件朋友圈.txt内容

7 4
3 1 2 3
2 1 4
3 5 6 7
1 6

依次处理每一条边即可

//计算 
void Cal()
{
	//文件读取数据 
	fstream inFile("朋友圈.txt",ios::in);
	if(!inFile)cout<<"fail to open file!"<<endl;
	int n,m;//总人数,社团个数 
	inFile>>n>>m;//cout<<"n:"<<n<<" m:"<<m<<endl;
	for(int i = 1; i <= n; i++){//全初始化为-1 
		father[i] = -1;
	}
	int sum,a,b,fa,fb;
	for(int i = 0; i < m; i++){//处理m条信息 
		inFile>>sum>>a;
		if(sum != 1){
			for(int j = 0; j < sum-1; j++){
			inFile>>b;
			fa = Find(a);//一次合并 
			fb = Find(b);
			Union(fa,fb);
			}
		}
	}
	inFile.close();
	int min = 999999;//由于初始值为-1,叠加后为负数,所以寻找最小值 
	for(int i = 0; i < n; i++){
		if(father[i] < 0){
			if(min > father[i]){
				min = father[i];
			}
		}
	}
	cout<<-min<<" ";
}

完整Code

#include<iostream>
using namespace std;
#include<fstream>

//并查集应用
//可用于求关联集合个数及其总个数 
//father数组最终状态:负数的个数表示朋友圈个数;数组值的绝对值表示该朋友圈人数 
int father[30005];//下标表示人的标号,值表示双亲节点 
//查找当前点的祖先,根节点 
int Find(int child) 
{
	int f = child;
	while(father[f] > 0){
		f = father[f];
	}
	//压缩路径,可有可无,不过可以提高效率 
	int j = child;
	while(j != f){
		father[j] = f;
		j = father[j]; 
	} 
	return f;
}
//合并两个点 
void Union(int fa,int fb)
{
	//优化:为了避免树的退化,每次将高度小的根接到高度大的根 
	if(father[fa] > father[fb]){
		father[fa] += father[fb];//个数相加 
		father[fb] = fa;//fb为fa父亲 
	}
	else{
		father[fb] += father[fa]; 
		father[fa] = fb; 
	}
}
//计算 
void Cal()
{
	//文件读取数据 
	fstream inFile("朋友圈.txt",ios::in);
	if(!inFile)cout<<"fail to open file!"<<endl;
	int n,m;//总人数,社团个数 
	inFile>>n>>m;//cout<<"n:"<<n<<" m:"<<m<<endl;
	for(int i = 1; i <= n; i++){//全初始化为-1 
		father[i] = -1;
	}

	int sum,a,b,fa,fb;
	for(int i = 0; i < m; i++){//处理m条信息 
		inFile>>sum>>a;
		if(sum != 1){
			for(int j = 0; j < sum-1; j++){
			inFile>>b;
			fa = Find(a);//一次合并 
			fb = Find(b);
			Union(fa,fb);
			}
		}
	}
	inFile.close();
	int min = 999999;//由于初始值为-1,叠加后为负数,所以寻找最小值 
	for(int i = 0; i < n; i++){
		if(father[i] < 0){
			if(min > father[i]){
				min = father[i];
			}
		}
	}
	cout<<-min<<" ";
}
int main()
{
	Cal();
	return 0;
}

收获

  • 不得不佩服前人的精妙思维,如此简单的结构却如此强大,果真大道至简
  • 必须锻炼自己明白算法思想就写出程序的能力
  • 学习算法时得明白他的来龙去脉,如何被创造而成,而不是仅仅学会如何使用
  • 同时也该多想想算法思想之间的关联性,异同之处,才可能融汇贯通。如哈夫曼树利用数组思想,和堆排序类似;二叉排序树先查找,后插入,与并查集先查找,再确定是否合并有些类似

猜你喜欢

转载自blog.csdn.net/qq_40738840/article/details/85889899