并查集简介:在一些应用问题中,需要将n个不同的元素划分为一组不相交的集合,在划分过程中需要反复查询某个元素归属于哪个集合。并查集就是一种适合于描述这类问题的抽象数据类型。
并查集主要支持的操作:
1. merge(Root1, Root2) //并操作;
2. get (x) //搜索操作;
3. UFS (s) //构造函数。
一般情形,并查集主要涉及两种数据类型:集合名类型和集合元素的类型。
在许多情况下,可以用整数作为集合名。如果集合中有n个元素,可以用1到n之间的整数表示集合元素。
对于并查集来说,每个集合用一棵树表示。
集合中每个元素的元素名分别存放在树的结点中,此外,树的每一个结点还有一个指向其双亲结点的指针。
根结点的父指针为负数,其绝对值表示集合中的元素个数。
// 并查集.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//并查集的基本操作
#include "pch.h"
#include <iostream>
#include<vector>
using namespace std;
const int defaultSize = 10;
class UFS {
private:
vector<int> parent;
public:
UFS(int size = defaultSize) {
//初始化:初始的时候每个结点各自为一个集合,
//parent[i]表示结点 i 的父亲结点,如果 parent[i]=i,我们认为这个结点是当前集合根结点。
for (int i = 0; i < size; i++)
{
parent.push_back(i);
}
}
int old_get(int x)//查找:查找结点所在集合的根结点,结点 x 的根结点必然也是其父亲结点的根结点。
{
while (parent[x]!=x)
{
x = parent[x];
}
return x;
}
void merge(int x, int y)//将两个节点所在的集合并起来,一般要首先检查两个节点是否属于同一个集合
{
x = old_get(x);
y = old_get(y);
if (x != y)// 不在同一个集合
{
parent[y] = x;
}
}
/*
前面的并查集的复杂度实际上在有些极端情况会很慢.
比如树的结构正好是一条链,那么最坏情况下,每次查询的复杂度达到了O(n)。
这并不是我们期望的结果。路径压缩的思想是,我们只关心每个结点的父结点,而并不太关心树的真正的结构。
这样我们在一次查询的时候,
可以把查询路径上的所有结点的father[i]都赋值成为根结点。
只需要在我们之前的查询函数上面进行很小的改动
*/
int get(int x)
{
if (parent[x] == x)
return x;
else
return parent[x] = get(parent[x]);
}
/*
路径压缩在实际应用中效率很高,
其一次查询复杂度平摊下来可以认为是一个常数。并且在实际应用中,我们基本都用带路径压缩的并查集。
*/
};
class WUFS {
/*
所谓带权并查集,是指结点存有权值信息的并查集。并查集以森林的形式存在,
而结点的权值,大多是记录该结点与祖先关系的信息。比如权值可以记录该结点到根节点的距离。
*/
/*
例题
在排队过程中,初始时,一人一列。一共有如下两种操作。
合并:令其中的两个队列 A,B 合并,也就是将队列 A 排在队列 B 的后面。
查询:询问某个人在其所在队列中排在第几位。
*/
/*
我们不妨设 size[]为集合中的元素个数,dist[]为元素到队首的距离,
*/
private:
vector<int> parent;
vector<int> size;
vector<int> dis;
public:
WUFS(int s)
{
for (int i = 0; i < s; i++)
{
parent.push_back(i);
dis.push_back(0);
size.push_back(1);
}
}
int get(int x)
{
if (parent[x] == x)
return x;
int y = parent[x];
parent[x] = get(y);//太巧妙了
dis[x] += dis[y];
return parent[x];
}
void merge(int x, int y)
{
x = get(x);
y = get(y);
if (x != y)
{
parent[x] = y;
dis[x] = size[y];
size[y] += size[x];
}
}
};
int main()
{
std::cout << "Hello World!\n";
}