并查集-C++实现

本文详细介绍了并查集的数据类型、表示方式及其基本操作,包括创建、查找、连接节点和检查连通性,还提供了数组实现的完整代码。通过实例展示了如何利用并查集解决连通性问题,是理解并查集核心概念和技术实践的好资料。
摘要由CSDN通过智能技术生成

并查集的抽象数据类型

并查集是一个包含若干个结点的集合,结点之间的关系有连通断开两种。
连通关系具有传递性:即A与B连通,B与C连通,则A与C连通。
由于连通关系的传递性,所有连通的结点构成一个连通集,如果一个并查集没有连通集,则该并查集是一个空集;如果一个并查集只有一个连通集,则该并查集全局连通;如果一个并查集有n个连通集,则该并查集被分割成n个部分。

连通集的抽象数据类型

一个连通集内的结点可以用结构描述,每个结点有两个域:
数据域:存储结点自身信息
指针域:储存父结点位置
注意,根结点的指针域不存储父结点位置,而是存储连通集的结点总数,原因在后文给出。

连通集的表示

连通集可以用其根结点表示,如某连通集 U{0, 1, 4, 6} 的根结点为0,则记连通集 U{0, 1, 4, 6} 为U(0)。

并查集的基本操作

Create 创建一个空的并查集
Find(X) 在并查集中查找X结点,返回X结点属于哪个集合
Union(X,Y) 连接X和Y结点
Check(X,Y) 检查X和Y结点是否连通
Check 检查全局连通性

并查集:数组实现

设一个并查集UnionFindSetMaxSize个结点,用数组array_father[MaxSize]描述结点信息:
n
个结点的父节点位置为array_father[n],故不需要数据域。

template<int MaxSize>
class UnionFindSet {
private:
	int array_father[MaxSize];		//指针域
	int find(int x);				//在并查集中查找X结点,返回X结点属于哪个集合
public:
	void Union(int x, int y);		//连接X和Y结点
	bool check(int x, int y);		//检查X和Y是否连通
	void check()const;				//检查全局连通性
};

连接X和Y结点

连接X和Y结点,相当于连接X和Y结点所在的连通集。前文提到:连通集可以用其根结点表示,故可以直接使U(X)的根结点指向U(Y)的根结点,或使U(Y)的根结点指向U(X)的根结点,那么到底让X指向Y还是让Y指向X呢?
前文提到:连通集内的结点是用树结构描述的,树的查找效率和树的规模有关,树的规模可以用树的高度或树的结点数描述,因为连通集的查找带有路径压缩(后文会提到),连通集所对应树的高度不会超过2,所以本文用树的结点数描述树的规模。
为了不使树的规模变得太大,应尽量将小树插入到大树上时。

template<int MaxSize>
void UnionFindSet<MaxSize>::Union(int x, int y) {
	int root[2] = { find(x),find(y) };	//查找X和Y结点所在连通集的根结点
	//如果没有找到X或Y的根结点,则X或Y不属于任何连通集	
	if (EOF == root[0] || EOF == root[1] || root[0] == root[1])return;
	//如果U(Y)的规模较大,则把U(X)连通到U(Y)上
	else if (array_father[root[0]] > array_father[root[1]]) {
		array_father[root[1]] += array_father[root[0]];
		array_father[root[0]] = root[1];
	}
	//如果U(X)的规模较大,则把U(Y)连通到U(X)上
	else {
		array_father[root[0]] += array_father[root[1]];
		array_father[root[1]] = root[0];
	}
}

检查X和Y结点是否连通

检查X和Y结点是否连通,相当于检查X和Y是否属于同一个连通集,相当于检查X和Y所在连通集的根结点是否相同。

template<int MaxSize>
bool UnionFindSet<MaxSize>::check(int x, int y)const {
	//如果X或Y越界,则X或Y不属于任何连通集
	if (x < 0 || x >= MaxSize || y < 0 || y >= MaxSize)return false;
	//查找X和Y结点所在连通集的根结点,检查它们是否相同
	return find(x) == find(y) ? true : false;
}

查找结点所在的集合:带有路径压缩

连通集内的结点是用树结构描述的,我们自然希望树的高度越矮越好,最好连通集的所有结点都直接连到根结点上。
当我们查找某个X结点时,它可能不是直接连在根结点上的,这时我们需要“记住”这个结点,然后继续访问X结点的父结点。
如果父结点也不是直接连到根结点上的,我们又“记住”父结点,然后继续访问父结点的父结点,以此类推。
当查找到根结点时,我们先把“记住”的所有结点的父结点设置成根结点,再返回根结点,这样X结点到根结点经过的所有结点,到根结点的路径都变成了1,这就完成了一次路径压缩
那怎么“记住”途径的结点呢,我们用堆栈实现。

template<int MaxSize>
int UnionFindSet<MaxSize>::find(int x) {
	//如果X越界,则X属于任何连通集
	if (x < 0 || x >= MaxSize)return EOF;
	stack<int>S;	//建立存储结点的堆栈
	//当X结点的父结点不是根结点时循环
	while (array_father[x] >= 0) {
		S.push(x);	//将途径结点压入堆栈
		x = array_father[x];	//继续访问途径结点的父结点
	}
	//把堆栈里所有结点的父结点设为根结点
	while (!S.empty())
		array_father[S.pop()] = x;
	return x;	//返回根结点
}

检查全局连通性

当全局连通时,只存在一个连通集,也就是所有节点中,只有一个根结点。

template<int MaxSize>
void UnionFindSet<MaxSize>::check()const {
	const int*it = array_father;
	int sum = 0;	//变量sum记录一共有几个根结点
	//全局扫描,统计根结点数量
	for (int count = 0; count != MaxSize; count++)
		if (*it++ < 0)sum++;
	//当只有一个根结点时,返回提示信息:全局连通
	//否则返回提示信息:全局有N个连通集
	if (1 == sum)cout << "The network is connected." << endl;
	else cout << "There are " << sum << " compenents." << endl;
}

完整代码

//并查集:数组实现
#ifndef OCUNIONFINDSET_H
#define OCUNIONFINDSET_H

#include<iostream>
using std::ostream;
using std::cout;
using std::endl;
#include"OCstack.h"
using OC::stack;

#ifndef _OC_BEGIN
#define _OC_BEGIN namespace OC{
#endif

#ifndef _OC_END
#define _OC_END }
#endif

#ifndef EOF
#define EOF -1
#endif

_OC_BEGIN

template<int MaxSize>
class UnionFindSet {
private:
	int array_father[MaxSize];		//指针域
	int find(int x);				//查找某个元素所在的集合
public:
	template<int _MaxSize>
	friend ostream&operator<<(ostream&output, UnionFindSet<_MaxSize> const&x);
	UnionFindSet();					//构造函数
	void Union(int x, int y);		//对X和Y所在的集合并运算
	bool check(int x, int y);		//检查X和Y是否连通
	void check()const;				//检查全局连通性
};

//查找某个元素所在的集合:带有路径压缩
template<int MaxSize>
int UnionFindSet<MaxSize>::find(int x) {
	if (x < 0 || x >= MaxSize)return EOF;
	stack<int>S;
	while (array_father[x] >= 0) {
		S.push(x);
		x = array_father[x];
	}
	while (!S.empty())
		array_father[S.pop()] = x;
	return x;
}

template<int MaxSize>
ostream&operator<<(ostream&output, UnionFindSet<MaxSize> const&x) {
	const int*it = x.array_father;
	for (int count = 0; count != MaxSize; count++)
		output << '[' << count << ':' << *it++ << ']';
	return output;
}

//构造函数
template<int MaxSize>
UnionFindSet<MaxSize>::UnionFindSet() {
	int*it = array_father;
	for (int count = 0; count != MaxSize; count++)
		*it++ = -1;
}

//对X和Y所在的集合并运算
template<int MaxSize>
void UnionFindSet<MaxSize>::Union(int x, int y) {
	int root[2] = { find(x),find(y) };
	if (EOF == root[0] || EOF == root[1] || root[0] == root[1])return;
	else if (array_father[root[0]] > array_father[root[1]]) {
		array_father[root[1]] += array_father[root[0]];
		array_father[root[0]] = root[1];
	}
	else {
		array_father[root[0]] += array_father[root[1]];
		array_father[root[1]] = root[0];
	}
}

//检查X和Y是否连通
template<int MaxSize>
bool UnionFindSet<MaxSize>::check(int x, int y)const {
	if (x < 0 || x >= MaxSize || y < 0 || y >= MaxSize)return false;
	return find(x) == find(y) ? true : false;
}

//检查全局连通性
template<int MaxSize>
void UnionFindSet<MaxSize>::check()const {
	const int*it = array_father;
	int sum = 0;
	for (int count = 0; count != MaxSize; count++)
		if (*it++ < 0)sum++;
	if (1 == sum)cout << "The network is connected." << endl;
	else cout << "There are " << sum << " compenents." << endl;
}

_OC_END

#endif
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值