05——并查集

1.可以解决连接问题,时间复杂度近似为O(n)

2.支持的操作

unionElements(p, q);		//将集合p、q归并
find(p);					//查找p属于哪个集合
isConnected(p,q);			//判断元素p和元素q是否所属一个集合

3.实现1(quick find)

数组中存储的是哪个集合

在这里插入图片描述

class UnionFind {

private:
	int *id; 
	int count; // 数据个数
public:
	// 构造函数
	UnionFind(int n) {
		count = n;
		id = new int[n];
		// 初始化, 每一个id[i]指向自己, 没有合并的元素
		for (int i = 0; i < n; i++)
			id[i] = i;
	}
	
	// 析构函数
	~UnionFind() {
		delete[] id;
	}
	
	// 查找过程, 查找元素p所对应的集合编号
	int find(int p) {
		assert(p >= 0 && p < count);
		return id[p];
	}
	
	// 查看元素p和元素q是否所属一个集合
	bool isConnected(int p, int q) {
		return find(p) == find(q);
	}
	
	// 合并元素p和元素q所属的集合
	void unionElements(int p, int q) {
		int pID = find(p);
		int qID = find(q);
		if (pID == qID)
			return;
		// 合并过程需要遍历一遍所有元素, 将两个元素的所属集合编号合并
		for (int i = 0; i < count; i++)
			if (id[i] == pID)
				id[i] = qID;
		}
};

4.实现2(quick union)

数组中存储的父节点的下标

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

class UnionFind{

private:
	int* parent;// parent[i]表示第i个元素所指向的父节点
	int count; // 数据个数
public:
	UnionFind(int count){
		parent = new int[count];
		this->count = count;
		// 初始化, 每一个parent[i]指向自己, 表示每一个元素自己自成一个集合
		for( int i = 0 ; i < count ; i ++ )
			parent[i] = i;
	}

	~UnionFind(){
		delete[] parent;
	}
	
	// 查找过程, 查找元素p所对应的集合编号
	int find(int p){
		assert( p >= 0 && p < count );
		// 不断去查询自己的父亲节点, 直到到达根节点
		// 根节点的特点: parent[p] == p
		while( p != parent[p] )
			p = parent[p];
		return p;
	}
	
	// 查看元素p和元素q是否所属一个集合
	bool isConnected( int p , int q ){
		return find(p) == find(q);
	}
	
	// 合并元素p和元素q所属的集合
	void unionElements(int p, int q){            //缺陷:总是将p的根节点指向q的根节点
		int pRoot = find(p);
		int qRoot = find(q);
		if( pRoot == qRoot )
			return;
		parent[pRoot] = qRoot;
	}
};

优化:unionElements函数

方式1:将节点少的根指向节点多的根
class UnionFind{

private:
	int* parent; // parent[i]表示第i个元素所指向的父节点
	int* sz; // sz[i]表示以i为根的集合中元素个数
	int count; // 数据个数
public:
	UnionFind(int count){
		parent = new int[count];
		sz = new int[count];
		this->count = count;
		for( int i = 0 ; i < count ; i ++ ){
			parent[i] = i;
			sz[i] = 1;
		}
	}

	~UnionFind(){
		delete[] parent;
		delete[] sz;
	}
	
	// 查找过程, 查找元素p所对应的集合编号
	int find(int p){
		assert( p >= 0 && p < count );
		// 不断去查询自己的父亲节点, 直到到达根节点
		// 根节点的特点: parent[p] == p
		while( p != parent[p] )
			p = parent[p];
		return p;
	}
	
	// 查看元素p和元素q是否所属一个集合
	bool isConnected( int p , int q ){
		return find(p) == find(q);
	}
	
	// 合并元素p和元素q所属的集合
	void unionElements(int p, int q){
		int pRoot = find(p);
		int qRoot = find(q);
		if( pRoot == qRoot )
			return;
		// 根据两个元素所在树的元素个数不同判断合并方向
		// 将元素个数少的集合合并到元素个数多的集合上
		if( sz[pRoot] < sz[qRoot] ){
			parent[pRoot] = qRoot;
			sz[qRoot] += sz[pRoot];
		}
		else{
			parent[qRoot] = pRoot;
			sz[pRoot] += sz[qRoot];
		}
	}
};
方式2:考虑层级

在这里插入图片描述

在这里插入图片描述

class UnionFind{

private:
	int* rank; // rank[i]表示以i为根的集合所表示的树的层数
	int* parent; // parent[i]表示第i个元素所指向的父节点
	int count; // 数据个数
	
public:
	UnionFind(int count){
		parent = new int[count];
		rank = new int[count];
		this->count = count;
		for( int i = 0 ; i < count ; i ++ ){
			parent[i] = i;
			rank[i] = 1;
		}
	}

	~UnionFind(){
		delete[] parent;
		delete[] rank;
	}
	
	// 查找过程, 查找元素p所对应的集合编号
	int find(int p){
		assert( p >= 0 && p < count );
		// 不断去查询自己的父亲节点, 直到到达根节点
		// 根节点的特点: parent[p] == p
		while( p != parent[p] )
			p = parent[p];
			return p;
	}
	
	// 查看元素p和元素q是否所属一个集合
	bool isConnected( int p , int q ){
		return find(p) == find(q);
	}
	
	// 合并元素p和元素q所属的集合
	void unionElements(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[qRoot] < rank[pRoot]){
			parent[qRoot] = pRoot;
		}	
		else{ // rank[pRoot] == rank[qRoot]
			parent[pRoot] = qRoot;
			rank[qRoot] += 1; // 此时, 我维护rank的值
		}	
	}
};

优化:find函数(路径压缩)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

class UnionFind{

private:
	int* rank;// rank[i]表示以i为根的集合所表示的树的层数
	int* parent; // parent[i]表示第i个元素所指向的父节点
	int count; // 数据个数
	
public:
	UnionFind(int count){
		parent = new int[count];
		rank = new int[count];
		this->count = count;
		for( int i = 0 ; i < count ; i ++ ){
			parent[i] = i;
			rank[i] = 1;
		}
	}
	
	~UnionFind(){
		delete[] parent;
		delete[] rank;
	}
	
	// 查找过程, 查找元素p所对应的集合编号
	int find(int p){
		assert( p >= 0 && p < count );
		// path compression 1
		while( p != parent[p] ){
			parent[p] = parent[parent[p]];
			p = parent[p];
		}
		return p;
		// path compression 2, 递归算法
		// if( p != parent[p] )
		// parent[p] = find( parent[p] );
		// return parent[p];
	}
	
	// 查看元素p和元素q是否所属一个集合
	bool isConnected( int p , int q ){
		return find(p) == find(q);
	}
	
	// 合并元素p和元素q所属的集合
	void unionElements(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[qRoot] < rank[pRoot]){
			parent[qRoot] = pRoot;
		}
		else{ // rank[pRoot] == rank[qRoot]
			parent[pRoot] = qRoot;
			rank[qRoot] += 1; // 此时, 我维护rank的值
		}
}
//最优情况(path compression 2, 递归算法)

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
并查集是一种常用的数据结构,用来解决集合的合并和查找问题。它具有快速的合并和查找操作,且在某些场景下比其他数据结构更为高效。 并查集包含以下几个关键步骤: 1. 初始化:首先将每个元素作为一个单独的集合,每个集合代表一个独立的元素。 2. 查找操作:通过查找操作可以快速找到某个元素所属的集合。这里使用了路径压缩的优化方法,即在查找的同时将当前元素直接指向根节点,以加速以后的查找操作。 3. 合并操作:当需要将两个元素所属的集合合并时,可以通过合并操作将一个元素的根节点指向另一个元素的根节点。这样就能实现将两个集合合并成一个集合的目的,从而实现集合的合并操作。 并查集的核心思想是通过维护每个元素的根节点,来判断元素之间的关系。如果两个元素具有相同的根节点,说明它们属于同一个集合;如果两个元素具有不同的根节点,说明它们属于不同的集合。 并查集可以解决一些实际问题,例如判断无向图中的两个节点是否连通,以及朋友圈的数量等。在这些问题中,我们可以使用并查集来维护每个节点所属的集合,从而更高效地进行相关的操作。 总之,并查集是一种有效处理集合合并和查找问题的数据结构,具有简单、高效的特点。通过合理地应用,并查集能够在解决某些实际问题时,提供更高效的算法实现。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值