并查集

森林:

森林是由若干棵互不相交的树组成,两棵树分别独立,没有交集

并查集:

并查集的结构和森林十分相似,是用于解决不相交集合如下若干几种操作的统称

1.MAKE_SET(x):初始化操作,建立一个只包含元素x的集合

2.UNION(x,y):合并操作,将包含x和y的集合合并成一个新集合

3.FIND_SET(x):查询操作,计算x所在集合

(一般用于求不相交集合的并集)

通常我们用有根树来表示集合,树中的每个结点对应集合中的一个成员

每个成员有一条指向父节点的边,整个有根树通过这些指向父节点的边来维护

每棵树的根就是这个集合的代表,并且这个代表的父节点就是它自己

 

基本操作:

初始化:

对每个元素都建立一个只包含该元素自己的集合,这意味着每个成员都是自身所在集合的代表,只需将父节点设为它自己

查询:

在不相交的森林中,并查集的查询操作,指的是查出指定元素所在有根树的根节点是谁

合并:

查找到代表元素,将其中一个根节点的父亲设置成另外一个根节点,如下图演示,我们完成了合并操作

集合的传递性(如果a和b同属一个集合,b和c同属一个集合,那么a和c也同属一个集合)

 

相应算法的实现:

创建一个并查集:

class DDJSSet {
private:
	int* father;   //用来保存每个结点的父结点
	int* rank;     //用来保存每个结点的秩
public:
	DDJSSet(int size) {
		father = new int[size];
		rank = new int[size];
		for (int i = 0; i < size; ++i) {
			father[i] = i;   //初始化时,将每个结点的父节点指向它自己
			rank[i] = 0;
		}
	}

	~DDJSSet() {
		delete[] father;
		delete[] rank;
	}
};

查询操作:

1.寻找当前结点的父结点

2.如果父节点是它本身,那么该点就是父节点,如果不是,则返回步骤1继续查找

合并操作:

1.分别获得传入的两个结点所在树的根结点

2.如果两个数所在根节点相同,说明他们在同一棵树上,返回false,结束合并操作

3,如果两个根节点不同,则将一个根节点的父节点指向另一个根节点,返回true结束操作

但是这样做会带来一些问题:我们任选一个结点指向一棵树的父结点,可能会导致一子棵树过长,最终整个并查集退化成一个链表,降低了查找效率,我们可以通过树的高度来判断连接顺序,将高度低的树的根节点连接到高度高的树的根节点

按秩合并:

1.利用一个数组保存每个结点所在树的高度的(初始化为0)

2.获得传入结点的树的根节点

3.若两个根节点不同,比较他们的高度

4.将高度低的树的父节点指向高度高的树的根结点

5.更新合并后的根节点的高度,返回

/*合并操作*/
	bool merge(int node1, int node2) {
		int ancestor1 = find_set(node1);
		int ancestor2 = find_set(node2);
		if (ancestor1 != ancestor2) {
			if (rank[ancestor1] > rank[ancestor2]) {
				swap(ancestor1, ancestor2);
			}
			father[ancestor1] = ancestor2;
			rank[ancestor2] = max(rank[ancestor2], rank[ancestor1] + 1);   //更新秩

			return true;
		}
		return false;
	}

 

路径压缩:

如上图所示,如果我们把每一个子节点都指向这个并查集的根节点,这样会大大地降低查找效率

在执行寻找根节点的函数中,我们稍作修改,将查找路径上的所有结点都指向最终查找到的根节点

/*路径压缩算法*/
	int find_set(int node) {
		if (father[node] != node) {
			father[node] = find_set(father[node]);
		}
		return father[node];
	}

 

整体代码:

#include <iostream>
#include <algorithm>
using namespace std;

class zDisjointSet {
private:
	int* father;
	int* rank;
public:
	zDisjointSet(int size) {
		father = new int[size];
		rank = new int[size];
		for (int i = 0; i < size; ++i) {
			father[i] = i;
			rank[i] = 0;
		}
	}
	~zDisjointSet() {
		delete[] father;
		delete[] rank;
	}

	/*路径压缩算法*/
	int find_set(int node) {
		if (father[node] != node) {
			father[node] = find_set(father[node]);
		}
		return father[node];
	}

	/*合并操作*/
	bool merge(int node1, int node2) {
		int ancestor1 = find_set(node1);
		int ancestor2 = find_set(node2);
		if (ancestor1 != ancestor2) {
			if (rank[ancestor1] > rank[ancestor2]) {
				swap(ancestor1, ancestor2);
			}
			father[ancestor1] = ancestor2;
			rank[ancestor2] = max(rank[ancestor2], rank[ancestor1] + 1);   //更新秩

			return true;
		}
		return false;
	}
};

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值