一、并查集
存储结构:数组
. 原理:
. 1.构建目标:利用数组的快速寻址的优势,将多个集合在数组上构建成无环非连通图,
. 各个连通分量的结点间以保存上级结点索引进行关联,
. 即树形结构,每个集合有且只有一个根结点;
. 2.根结点查找:查找集合元素的根结点;
. 3.连通性判断:通过查找两个集合元素的根结点,判断两集合是否连通;
. 4.集合合并:将两个非连通的集合合并,选取其中任一集合的根结点作为合并后集合的根结
. 用途:
. 1.查:快速判断两个元素所在集合是否连通,压缩路径后,时间复杂度为 O(1);
. 2.并:快速合并n个集合中存在交集的集合,最终统计出无交集集合个数。
二、代码
//文件名:"UFS.h"
#pragma once
#ifndef UFS_H_
#define UFS_H_
//参考博客:
//https://blog.csdn.net/qingdou_aixuexi/article/details/52695395
//https://blog.csdn.net/niushuai666/article/details/6662911
//https://blog.csdn.net/u014682691/article/details/52004316
//https://blog.csdn.net/wusecaiyun/article/details/49079701
/*
. 并查集类 (Union Find Set)
. 存储结构:数组
. 原理:
. 1.构建目标:利用数组的快速寻址的优势,将多个集合在数组上构建成无环非连通图,
. 各个连通分量的结点间以保存上级结点索引进行关联,
. 即树形结构,每个集合有且只有一个根结点;
. 2.根结点查找:查找集合元素的根结点;
. 3.连通性判断:通过查找两个集合元素的根结点,判断两集合是否连通;
. 4.集合合并:将两个非连通的集合合并,选取其中任一集合的根结点作为合并后集合的根结点;
*/
class UFS
{
private:
int * arr = NULL; //数组指针
//int * arr_num = NULL; //各结点的孩子数计数数组
int length = 0; //数组长度
public:
UFS(int length); //构造函数
~UFS(); //析构函数
int FindRoot(int e); //寻找元素所在集合的根
int Union(int e1, int e2); //合并集合
};
#endif // !UFS_H_
//文件名:"UFS.cpp"
#include "stdafx.h"
#include "UFS.h"
using namespace std;
UFS::UFS(int length)
{
/*
. 构造函数
*/
//0.初始化数组长度
this->length = length;
//1.创建数组对象
arr = new int[length];
//2.初始化数组:其下标值为下标索引
for (int i = 0; i < length; i++)
{
arr[i] = i;
}
}
UFS::~UFS()
{
/*
. 析构函数
*/
//销毁数组
delete[] arr;
}
int UFS::FindRoot(int e)
{
/*
. 寻找元素所在集合的根
. 注:根结点位置:arr[i] == i;其它结点位置:arr[i] == pi (上级结点下标)
*/
//遍历向上级寻根
while (arr[e] != e)
{
e = arr[e];
}
return e;
}
int UFS::Union(int e1, int e2)
{
/*
. 合并集合
. 入参:
. int e1, e2: 两结点元素下标
. 出参:
. int : 0|集合连通不合并 1|集合非连通合并
*/
//1.分别查找e1 e2 元素集合根
int r1 = FindRoot(e1);
int r2 = FindRoot(e2);
//2.若两集合连通,不合并;否则合并
if (r1 == r2)
return 0;
//3.合并集合(右集合根结点指向左集合根结点)
arr[r2] = r1;
//4.返回合并成功
return 1;
}
//文件名:"UFS_Test.cpp"
#include "stdafx.h"
#include <iostream>
#include "UFS.h"
using namespace std;
int main()
{
UFS * ufs = new UFS(6);
ufs->Union(1, 2);
ufs->Union(2, 3);
ufs->Union(4, 5);
cout << "显示并查集结点:" << endl;
for (int i = 0; i < 6; i++)
{
cout << i << "(" << ufs->FindRoot(i) << ") ";
}
cout << endl;
ufs->Union(3, 5);
cout << "合并(3,5)后,显示并查集结点:" << endl;
for (int i = 0; i < 6; i++)
{
cout << i << "(" << ufs->FindRoot(i) << ") ";
}
return 0;
}
三、输出结果
四、参考博客
1.仅集合根结点处记录孩子数,并用负数:
https://blog.csdn.net/qingdou_aixuexi/article/details/52695395
2.很形象的“畅通工程”问题,“寻根”操作中用到了压缩路径(即压缩成子结点直指根结点):
https://blog.csdn.net/niushuai666/article/details/6662911
3.用两个数组实现,增加了统计各个结点孩子数的数组,合并时以孩子数少的根结点向孩子多的根合并:
https://blog.csdn.net/u014682691/article/details/52004316
4.数组元素对象化,增加每个结点的parent域和孩子数域,也使用了压缩路径:
https://blog.csdn.net/wusecaiyun/article/details/49079701