并查集实现及使用
并查集:
将n个不同的元素划分成一组不相交的集合,开始每个元素是一个单元素集合,之后按照一定顺序将属于同一组元素的集合合并,期间需要反复用到查询某个元素属于哪个集合的运算,称此类问题的抽象数据类型为并查集;主要运算:1、合并两个集合;2、查找元素所属集合;
可使用父节点数组实现并查集,具体代码根据题目分析;
第十次作业:
1、(n个节点之间通过不同连接种类联通,两节点间连接联通的种类数)题目链接
解题思路:对不同的连接方式作为一个总并查集集合,看里面所寻找的的点是否属于同一个集合;
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<malloc.h>
#include<memory.h>
#include<iostream>
#define MAX_SIZE 100005
using namespace std;
typedef struct aufs* UFS;
typedef struct aufs
{
int *parent;
int *height; //合并两棵树时根据高度大小将小树合并到大树下面;
}Aufs;
UFS UFinit(int size,UFS U)
{
int i;
U->parent=(int*)malloc((size+1)*sizeof(int));
U->height=(int*)malloc((size+1)*sizeof(int));
for(i=0;i<=size;i++)
{
U->parent[i]=i;
U->height[i]=0;
}
return U;
}
int UFfind(int e,UFS U)
{
if(U->parent[e]!=e)U->parent[e]=UFfind(U->parent[e],U); //按照路径寻找根节点,一次将路径上所有节点的根节点更新;
return U->parent[e];
}
void UFunion(int i,int j,UFS U) //通过比较i和j的节点树的高度大小比较对其进行合并 ;
{
int fa1=UFfind(i,U),fa2=UFfind(j,U);
if(fa1==fa2)return;
if(U->height[fa1]>U->height[fa2])U->parent[fa2]=fa1; //将高度小的节点树并到高度大的节点树下面;
else
{
U->parent[fa1]=fa2;
if(U->height[fa1]==U->height[fa2])U->height[fa2]++; //如果两个 节点树高度相等,则并之后父节点高度应该增加1;
}
}
int main()
{
UFS u[11];
int n,m,i,j;
scanf("%d%d",&n,&m);
for(i=0;i<11;i++)
{
u[i]=new Aufs;
UFinit(n,u[i]);
}
for(i=0;i<m;i++)
{
int n1,n2,tran;
scanf("%d%d%d",&n1,&n2,&tran);
UFunion(n1,n2,u[tran]);
}
int que;
scanf("%d",&que);
for(i=1;i<=que;i++)
{
int que1,que2;
scanf("%d%d",&que1,&que2);
int sum=0;int k;
for(j=1;j<=10;j++)
{
// for(k=0;k<n;k++)printf("%d ",u[j]->parent[k]);
// printf("\n\n");
int tmp1=UFfind(que1,u[j]);
int tmp2=UFfind(que2,u[j]);
// printf("%d %d\n",tmp1,tmp2);
if(tmp1==tmp2)sum++;
}
printf("%d\n",sum);
}
return 0;
}
以上在合并时时根据两棵节点树的高度大小合并的,还可以根据其他的判断加以优化防止出现如果不优化的最差情况是一条链;
其次在查询函数中使用了压缩路径(通过递归回溯的形式对每条搜索的路径每次都全部更新其根节点),有时题目要求不能压缩路径则需改动;
2、(总共初始n个单元素集合,其中存在朋友关系,求不同集合数,过程中有元素数列按顺序离开,不断更新求集合数;)题目链接
解题思路:并查集主要运算式插入元素合并以及寻找元素,而此题的并查集涉及删除,因此需要将删除转化成插入问题,就是顺序离开变成逆序插入问题;
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<vector>
#include<iostream>
using namespace std;
typedef struct aman* Man;
typedef struct aman
{
int parent;
int height;
int leave;
// int left;
vector<int>friends;
}Aman;
Aman men[10005];
int teams=0;
int Left[10005];
int team[10005];
void init(int n)
{
int i;
for(i=0;i<n;i++)
{
men[i].parent=i;
men[i].height=0;
men[i].leave=0;
// men[i].left=0;
}
}
int Find(int man)
{
if(men[man].parent!=man)men[man].parent=Find(men[man].parent);
return men[man].parent;
}
void Union(int a,int b) //将a和b合并;
{
int fa1=Find(a),fa2=Find(b);
if(fa1==fa2)return ;
if(men[fa1].height>men[fa2].height)
{
men[fa2].parent=fa1;
teams--; //将两个集合合并时,如果他们不是同一个team,那么合并之后team数量应该减1;
}
else
{
men[fa1].parent=fa2;
if(men[fa1].height==men[fa2].height)
{
men[fa2].height++;
}
teams--; //将两个集合合并时,如果他们不是同一个team,那么合并之后team数量应该减1;
}
}
int main()
{
int n,m,i,j;
scanf("%d%d",&n,&m);
init(n);
for(i=0;i<m;i++)
{
int man1,man2;
scanf("%d%d",&man1,&man2);
men[man1].friends.push_back(man2); //两者是朋友关系,将其互相存入其朋友数组中;
men[man2].friends.push_back(man1);
}
int que;
scanf("%d",&que);
for(i=0;i<que;i++)
{
scanf("%d",&Left[i]); //先要将离去的人的顺序存储,便于后续逆序插入使用;
men[Left[i]].leave=1;
}
teams=n-que; //初始时都是单个team,除了将要离开的人总共初始是“n-将离开的人”,后续不断合并,team数量将减少;
for(i=0;i<n;i++) //先对所有不离开的人进行朋友team的合并,合并过程更新当前team数量;
{
if(men[i].leave==0) //判断这个人是否会离开;
{
for(j=0;j<men[i].friends.size();j++)
{
if(men[men[i].friends[j]].leave==0) //不离开的这个人的朋友也不离开,他们两个此时才能合并;
Union(i,men[i].friends[j]);
}
}
}
int k=0; //用k表示除了不离开的人之外从后往前不断加入的人数;
team[k]=teams; //上面已经计算出所有不离开的人中(即后续加入0个人)存在的team数量;
// printf("此时team数:%d\n",teams);
for(i=que-1;i>=0;i--) //逆序不断插入人进入,更新每次插入后当前的team数量并存储;
{
teams++; //先插入未合并,team数量加一;
men[Left[i]].leave=0; //此时插入的人就是后续不离开的的人,leave应该改为0;
for(j=0;j<men[Left[i]].friends.size();j++)
{
if(men[men[Left[i]].friends[j]].leave==0)
Union(Left[i],men[Left[i]].friends[j]);
}
k++; //插入一个人k(插入的人数)加一,更新插入不同人数后存在的team数量;
// printf("此时team数:%d\n",teams);
team[k]=teams;
}
for(i=que;i>=0;i--) //表示从插入que个人一直到插入0个人(即离开0个人到离开que个人)的team数;
printf("%d\n",team[i]);
return 0;
}