码迷,mamicode.com
首页 > 其他好文 > 详细

并查集

时间:2020-04-22 13:27:37      阅读:66      评论:0      收藏:0      [点我收藏+]

标签:树形结构   The   lang   建立   lse   tin   递归   turn   压缩   

问题提出:n个人,组成多个团体,给出关系链,多个查询,其中两人是否为同一团体
解决方法:
1. 构建一个无向图,同一个团体加同样的标记,合并时复杂度很高,查询为O(1)

2. 并查集,树形结构的数组(不相交集合的合并,查询等问题):建立集合,查询元素所在集合,合并集合...

初始化:f(i) = i
并查集处理的问题:x个元素分别属于y个集合,不断给出元素间的联系,可以实时统计元素间的关系。可以使用数组,链表等来实现。
  • 对于普通的并查集可以使用路径压缩优化,但对于加权并查集需特殊处理
const int kN = 1010;
int x,f[kN];
//加权并查集特殊处理,关系间存在权值,有顺序,不可路径压缩,另外一个数组存储权值 
//路径压缩 f[kid] = f[father],减少查询时的复杂度,所有子节点均指向代表元素 
//不使用路径压缩,最坏情况下会退化为一个链表 
//初始化 自己代表自己 
void init(){
	for(int i = 1; i <= n; i++) f[i] = i;
} 
//查找集合的代表元素 root,查找过程中进行路径压缩 
int find(int x){
	//当前节点 - 父节点 -> 找到root 
	//非递归
	int r = x,t;
	while(r != f[r]){//寻找代表元素  
		r = f[r]; 
	} 
	while(x != r){
		t = f[x];
		f[x] = r;
		x = t;
	} 
	return r;
	//递归,速度较慢 
	if(f[x] != x)  f[x] = find(f[x]);
	else return f[x];
}
//判断两元素是否为同一集合 
bool judge(int x,int y){
	if(find(x) == find(y)) return true;
	return false; 
}
//集合合并,修改代表元素指向即可 
//合并方法二:按秩合并,深度小的树合并到深度大的树,深度小的树的root指向深度大的树的root,防止退化 
void unionn(int x,int y){
	x = find(x);
	y = find(y);
	f[x] = y;
} 
//x加入y所在的集合 
void join(int x,int y){
	int rx = find(x),ry = find(y);
	if(rx != ry) f[rx] = ry;
} 

HDU1232 畅通工程

  • 图中存在n个连通块,修建n - 1条条路,每个连通块是一个集合
const int kN = 1010;
int n,m,x,s,e,ans,f[kN];
map<int,bool> book;
//初始化 自己代表自己 
void init(){
	for(int i = 1; i <= n; i++) f[i] = i;
} 
//查找集合的代表元素 root,查找过程中进行路径压缩 
int find(int x){
	//当前节点 - 父节点 -> 找到root 
	//非递归
	int r = x,t;
	while(r != f[r]){//寻找代表元素  
		r = f[r]; 
	} 
	while(x != r){
		t = f[x];
		f[x] = r;
		x = t;
	} 
	return r;
}
//x加入y所在的集合 
void join(int x,int y){
	int rx = find(x),ry = find(y);
	if(rx != ry) f[rx] = ry;
} 

int main(void){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	
	while(cin >> n){
		if(!n) break;
		cin >> m;
		init();
		for(int i = 0; i < m; i++){
			cin >> s >> e;
			join(s,e);		
		}
		book.clear();
		for(int i = 1; i <= n; i++) book[find(i)] = true;
		cout << book.size() - 1 << endl;
	}
	return 0;
}

(无向图)最小生成树de两种贪心算法
kruskal算法:并查集判断成环,边权升序排列,从前到后枚举,当这条边加入后不成环则可加入。

? n个节点的连通图找到n-1条边,使所有节点都可到达,且这些边的权值和最小。

int x,f[kN];
struct edge{
	int form,to,value;
} 
int kruskal(edge *edges,int m){//边集 大小 
	sort(edges,edges + m,cmp);//按边权升序排列
	int r = 0;//权
	for(int i = 0; i < m; i++){
		if(find(deges[i].from) == find(edges[i].to)) continue;//成环,在同一连通块中
		r += edges[i].value;//边权加入权值和
		unionn(edges[i].from,edges[i].to);//合并连通块 
	} 
	return r;
}

prim算法:

  • dist[n]数组,存储当前节点到各边的距离
  • O(n^2),一个选择集合,一个边的集合。任一节点开始,枚举它可以到达的所有边,寻找最小权值,搜索这条边到达的节点...
int edges[N][N],dist[N];
bool vis[N];
void add_edge(int form,int to,int value){
	edges[form][to] = max(edges[from][to],value);
	edges[to][form] = max(edgee[to][form],value);
}
void init(){
	memset(edges, - 1, sizeof(edges));
	memset(dist, - 1, sizeof(dist));
	memset(vis, false, sizeof(vis));
}
int prim(int n){
	vis[1] = true;//任一节点开始
	for(int i = 2; i <= n; i++) dist[i] = edges[1][i];
	int ret = 0;
	for(int i = 2; i <= n; i++){
		int x = 1;
		for(int j = 1; j <= n; j++){//寻找最小权值 
			if(!vis[j] && dist[j] > dist[x]) x = j;
		}
		ret += dist[x];
		vis[x] = true;
		for(int j = 1; j <= n; j++){//更新dist x -> 其他节点 
			if(!vis[j]) dist[j] = max(dist[j],edges[x][j]);
		}
	}
	return ret;
}

待学习:可持久化线段树,可持久化并查集

并查集

标签:树形结构   The   lang   建立   lse   tin   递归   turn   压缩   

原文地址:https://www.cnblogs.com/honey-cat/p/12751209.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!