并查集及相关改进的C++实现

并查集是一种不一样的树形结构,它主要的作用是解决连接问题。比如判断下图中的两个节点是否相连,如果是相邻的两个点那么可以很快判断出来,但是对于相隔较远的点就需要使用并查集来判断了。在现实生活中我们主要使用连接问题判断网络之间的连接关系,这种网络包括计算机网络以及社交网络等等。
在这里插入图片描述

实现

在并查集中我们需要实现的功能主要就是

  1. 实现两个元素合并为同一个集合下 u n i o n ( p , q ) union(p,q) union(p,q)
  2. 查看某个元素属于哪一个集合 f i n d ( p ) find(p) find(p)
  3. 判读两个元素是否连接 i s C o n n e c t e d ( p , q ) isConnected(p,q) isConnected(p,q)

对于连接问题,我们首先将元素编号,那么连接 问题中的所有点就可以用一个数组来表示,通过对数组值的修改就可以实现并查集了。在执行union操作时,如果将p 和 q 连接,那么相当于是将p的集合和q的集合进行了合并,所以p集合中的所有ID都需要改为q集合的ID。

#include <iostream>
class UnionFind{
private:
	int* id;
	int count;
public:
	UnionFind(int n){
		id = new int[n];
		count = n;
		for(int i=0; i<n; i++)
			id[i] = i;        //每个元素属于集合i,即每个元素所属集合不同
	}
	~UnionFind() { delete[] id; }
	int find(int p){
		return id[p];
	}
	bool isConnected(int p , int q){
		return find(p) == find(q);
	}
	void unionElement(int p , int q){
		int pId = id[p];
		int qId = id[q];
		if(pId == qId)
			return;
		for(int i=0; i<n; i++){
			if(id[i] == pId)
				id[i] = qId;
		}
	}
}

优化

在上面的实现过程中,因为需要对每个元素都进行遍历,所以每次union的复杂度为 O ( n ) O(n) O(n),如果对于 n n n个数据都进行union操作,那么复杂度就是 O ( n 2 ) O(n^2) O(n2)。而下面采用的Quick Find的思路可以对这种情况进行一定的优化。
具体的思想是将每个元素看做一个节点,如果两个节点是连接状态,就将其中一个节点的指针指向另一个节点。而在初始状态,每个节点的指针都指向自己。如图所示,表示1,2,3的连接状态,可以将1,3的指针分别指向2,同时保证2的指针指向自己,这样就能保证一个完整的连接。
在这里插入图片描述
因为这里的指针只需要指向父亲节点,所以仍然可以使用数组进行实现。假设每个元素初始父亲节点指向自己。
在这里插入图片描述
在这里插入图片描述
此时如果需要union(4,3),只需要将4的元素修改为3即可。
在这里插入图片描述
在这里插入图片描述
那么这里的实现思路就简单了,每次进行union操作时,只需要找到两个节点的根节点,然后将一个根节点指向另一个根节点就可以了。

#include <iostream>
class UnionFind{
private:
	int* parent;
	int count;
public:
	UnionFind(int n){
		parent = new int[n];
		count = n;
		for(int i=0; i<n; i++)
			parent[i] = i;        
	}
	~UnionFind() { delete[] parent; }
	int find(int p){
		while(p != parent[p]){
			p = parent[p];
		}
		return p;
	}
	bool isConnected(int p , int q){
		return find(p) == find(q);
	}
	void unionElement(int p , int q){
		int pRoot = find(p);
		int qRoot = find(q);
		if(pRoot == qRoot)
			return;
		parent[pRoot] = qRoot;
	}
}

基于size的优化

但是在上述过程中,如果在合并过程中,将数量大的连接到数量小的上就会导致整个集合的高度提高,那么在find操作中需要向上寻找的次数就会增多,导致遍历时间增加。那么这里可以使用size的优化,将size数量较小的集合连接到数量较大的集合上。
在这里插入图片描述
实现方式也很简单只需要添加一个size的数组,每次union操作对size进行维护就可以了。

#include <iostream>
class UnionFind{
private:
	int* parent;
	int count;
	int* size;  //size[i]表示以i为根的集合元素
public:
	UnionFind(int n){
		parent = new int[n];
		size = new int[n];
		count = n;
		for(int i=0; i<n; i++){
			parent[i] = i;
			size[i] = 1;     
		}   
	}
	~UnionFind() { 
		delete[] parent; 
		delete[] size;
	}
	int find(int p){
		while(p != parent[p]){
			p = parent[p];
		}
		return p;
	}
	bool isConnected(int p , int q){
		return find(p) == find(q);
	}
	void unionElement(int p , int q){
		int pRoot = find(p);
		int qRoot = find(q);
		if(pRoot == qRoot)
			return;
		if(size[pRoot] < size[qRoot]){
			parent(pRoot) = qRoot;
			size(qRoot) + = size(pRoot);
		}
		else{
			parent(qRoot) = pRoot;
			size(pRoot) + = size(qRoot);
		}
	}
}

基于rank的优化

但是还有可能存在一种极端情况,假如集合A节点较少,但是层数比较高,而B集合节点多,但是层数少,若将A集合连接到B上,则会导致总体的层数增加,所以此时最合适的应该是基于高度判断哪一个集合作为根。
在这里插入图片描述
在这里插入图片描述
实现方法同上,只是需要对rank数组进行维护。

#include <iostream>
class UnionFind{
private:
	int* parent;
	int count;
	int* rank;  //size[i]表示以i为根的集合元素
public:
	UnionFind(int n){
		parent = new int[n];
		rank = new int[n];
		count = n;
		for(int i=0; i<n; i++){
			parent[i] = i;
			rank[i] = 1;     
		}   
	}
	~UnionFind() { 
		delete[] parent; 
		delete[] rank;
	}
	int find(int p){
		while(p != parent[p]){
			p = parent[p];
		}
		return p;
	}
	bool isConnected(int p , int q){
		return find(p) == find(q);
	}
	void unionElement(int p , int q){
		int pRoot = find(p);
		int qRoot = find(q);
		if(pRoot == qRoot)
			return;
		if(rank[pRoot] < rank[qRoot]){
			parent(pRoot) = qRoot;
		}
		else if(rank[pRoot] > rank[qRoot]{
			parent(qRoot) = pRoot;
		}
		else{
			parent(pRoot) = qRoot;
			rank[qRoot] + = 1;
		}
	}
}

路径压缩

上述操作都是针对union的,但是对于find并没有采取优化方式。对于下图这种一长串的集合,find操作时比较耗时的。此时可以在寻找时每次向上2个节点寻找,如果**没有到根节点的话,将当前节点的父节点赋值为父节点的父节点。**这样在find的过程中就将整个集合的高度压缩了。
在这里插入图片描述
在这里插入图片描述
因为只对find操作进行修正所以只需要修改find函数即可。

int find(int p){
		while(p != parent[p]){
			parent[p] = parent[parent[p]];
			p = parent[p];
		}
		return parent[p];
	}

路径压缩虽然对rank的值有影响,但是在实践中其实并不影响。因为在find的过程中,每个集合的高度都发生了减小,所以rank的相对大小依然保持恒定,并不影响总体性能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值