并查集的基本操作

1. 定义

并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题。
并:即union,合并两个集合;
查:即find,判断两个集合是否在同一集合;
集:即set,集合。
并查集用一个数组father[n]实现,father[i]表示元素i的父亲结点。

2. 基本操作

2.1 初始化

最初,每个元素都是一个独立的集合,所以把每个元素的父结点都初始化为元素本身。

for(int i=1;i<=n;i++)
  father[i] = i;

2.2 查找

查找给定结点的根结点。一般使用递归或递推的方式查找根结点,即反复寻找结点的父结点,直到找到father[i]==i为止。

//使用递归方式查找根结点
int findFather(int i)
{
    if(father[i] == i)
      return i;
    else
      return findFather(father[i]);
}
//使用递推方式查找根结点
int findFather(int i)
{
    while(father[i] != i)
      i = father[i];
    return i;
}

当元素数量很多时,使用以上查找方式效率较低,为了优化代码,可以把当前查询结点的路径上的所有结点的父亲都指向根结点。

//使用递归方式查找根结点
int findFather(int i)
{
    if(i == father[i])
      return i;
    else
    {
        int f = findFather(father[i]);
        father[i] = f;
        return f;
    }
}
//使用递推方式查找根结点
int findFather(int i)
{
    int x = i;
    while(i != father[i])
      i = father[i];
    while(x != father[x])
    {
    int y = x;
    x = father[x];
    father[y] = i;
    }
    return i;
}

2.3 合并

判断两个元素是否属于同一集合(即根结点是否相同),只有两个元素不属于同一个集合时才需要合并,合并过程就是将其中一个集合的根结点改为另一个集合的根结点(只改根结点)。

void Union(int a,int b)
{
	int faA=findFather(a);
	int faB=findFather(b);
	if(faA!=faB)
	  father[faA]=faB;
}

3、路径压缩

①、递推实现

int findFather(int x) 
{
	int a=x;
	while(x!=father[x])
	  x=father[x];
	while(a!=father[a])  //从底向上,再走一遍! 
	{
		int z=a;  //z暂时保存a的值 ps:可以想象为两个指针 
		a=father[a];   //a向上走一步 
		father[z]=x;
	}	
}

 ②、递归实现

int findFather(int v) 
{
	if(v==father[v]) 
      return v;
	else
	{
		int F=findFather(father[v]);
		father[v]=F;
		return F;
	}
}

总代码:
 

#include<bits/stdc++.h>
using namespace std;
template<class T>
struct DisjointSet{
	int *parent;
	T *data;
	map<T,int> m;
	int capacity;
	int size;
	DisjointSet(int max=1000){
		capacity=max;
		size=0;
		parent=new int[max+1];
		data=new T[max+1];
	}
	~DisjointSet(){
		delete [] parent;
		delete [] data;
	}
	bool insert(T x){
		if(size==capacity) return false;
		size++;
		data[size]=x;
		parent[size]=-1;
		m[x]=size;
		return true;
	}
	int getIndex(T x){
		for(int i=1;i<=size;i++)
		  if(data[i]==x)
		    return i;
		return -1; 
	}
	int find(T x){
		typename map<T,int>::iterator it;
		it=m.find(x);
		if(it==m.end()) return -1;
		int i,rt;
		i=rt=it->second;
		while(parent[rt]>0)
		  rt=parent[rt];
		int tmp;
		for(;i!=rt;i=tmp){
			tmp=parent[i];
			parent[i]=rt;
		}
		return rt;
	}
	void unionSet(T x,T y){
		int rx,ry;
		rx=find(x);
		ry=find(y);
		if(rx==-1||ry==-1) return ;
		if(rx==ry) return ;
		if(parent[rx]<parent[ry]){
			parent[rx]+=parent[ry];
			parent[ry]=rx;
		}
		else{
			parent[ry]+=parent[rx];
			parent[rx]=ry;
		}
	}
	void print(){
		cout<<"当前并查集:"<<endl;
		for(int i=1;i<=size;i++)
		  cout<<i<<"\t";
		cout<<endl;
		for(int i=1;i<=size;i++)
		  cout<<parent[i]<<"\t";
		cout<<endl;
		for(int i=1;i<=size;i++)
		  cout<<data[i]<<"\t";
		cout<<endl;
	}
};
int main(){
	DisjointSet<string> s;
	int n,m;
	cout<<"请输入学生个数n,和合并对数m:";
	cin>>n>>m;
	for(int i=0;i<m;i++){
		string a,b;
		cin>>a>>b;
		if(i>0){
			if(s.find(a)==-1)
		      s.insert(a);
			if(s.find(b)==-1)
		      s.insert(b);
		}
		else{
			s.insert(a);
			s.insert(b);
		}
		s.unionSet(a,b);
	}
	s.print();
	int sum=0,min=0x9999999,minx;
	for(int i=1;i<=s.size;i++){
		if(s.parent[i]<0){
			sum++;
			if(min>s.parent[i])
			  min=s.parent[i],minx=i;
		}
	}
	cout<<"一共有"<<sum<<"个学校的学生."<<endl;
	cout<<"人数最多的学校的学生:";
	for(int i=1;i<=s.size;i++)
	  if(s.parent[i]==minx||s.parent[i]==min)
	    cout<<s.data[i]<<",";
	return 0;
}

以上并查集代码是根据此题实现:

                                                       实例:推断学生所属学校

  某个比赛现场有来自不同学校的N名学生,给出M对“两人同属一所学校”的关系,请推断学校数量,并给出人数最多的学校的学生名单。

输入格式:

  先输入一个在[2,1000]范围的整数N,然后是N个用空格间隔姓名。接下来一行是正整数M,然后是M行,每行两个人名,表示同属一所学校。

输出格式:

  先输出学校的数量,在下一行输出人数最多的学校学生名单。

样例:

n=8;

 OK,今天的并查集就讲到这里了,大家再见!

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙星尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值