并查集是一种高级数据结构,在课堂上学习老师并不会讲到,这次刷Leetcode,发现自己还没有涉及到并查集相关内容,于是经过学习《算法导论》,进一步了解了并查集的相关内容,并且手写了并查集的模板,中间遇到了许多问题,例如内存分配的问题,内存防止泄露的问题,最后一步一步解决问题,不仅学到了算法,而且对c++智能指针有了进一步认识并且能够使用智能指针解决问题,因此在这里汇总一下。
并查集是一种用于不相交集合数据结构,它维护了一个不相交动态的集合\(\xi=\begin{Bmatrix}s1,s2,...,sk\end{Bmatrix}\).我们在其中使用一个代表来表示每一个集合,它是这个集合的某个成员。(这边一定要注意对代表的认识)并查集一共有三个基本操作:
#include <iostream>
#include <vector>
using namespace std;
/*
最简单的并查集
*/
const int n = 20; // 随便写一个20
vector<int> sets(n, 0);
void make_set(vector<int> &sets){
for(int i = 0; i < sets.size(); i++){
sets[i] = i;
}
}
int find_set(vector<int> &sets, int x){
if(sets[x] == x){
return sets[x];
}else{
return find_set(sets, sets[x]);
}
}
void union_set(vector<int> &sets, int x, int y){
sets[find_set(sets, x)] = find_set(sets, y);
}
// 统计并查集中集合的个数,并查集数组中没有被改变的数的个数便是并查集的个数
int num_set(vector<int> &sets){
int res = 0;
for(int i = 0; i < sets.size(); i++){
if(sets[i] == i){
i++;
}
}
return res;
}
/*
未完成的部分:.h文件书写 以及最后优化
接下来是优化后的并查集,准备用树进行书写
*/
// 先不用模板进行书写,随后使用模板进行改写
// 按秩合并和路径压缩
#include <iostream>
#include <vector>
#include <memory>
using namespace std;
// 问题:仅仅实现了并查集的一个集合,并没有对并查集进行整个初始化,并且少了计数能力
// 需求:合理实现并查集,并能够作为一个并查集模板供后期使用
struct set{
int value;
set*p; //指向父亲节点
int rank;
set():value(0), p(this), rank(0) { }
set(int x):value(x), p(this), rank(0) { }
/*
尝试用智能指针,但是失败了,原因:智能指针非必要
引发的思考(参数列表不能set*x = this)?
因为一个对象的构造分为两部分:内存分配和初始化
在参数里面内存还没有分配,在初始化列表中内存已经分配,可以使用this
*/
set(set *x):value(0), p(x), rank(0){ }
bool operator==(set &temp) const{ // 类内重载相等运算符
return this->value == temp.value;
}
bool operator!=(set &temp) const{
return this->value != temp.value;
}
};
// 带有路径压缩的 find_sets 方法
set* outer_find_sets(set*x){ // 两趟方法,先逐步找到根节点,再给每一个节点进行更新
if( x -> p != x){
x -> p = outer_find_sets(x -> p);
}
return x->p;
}
// 按秩合并
void link_set(set*x, set*y){
if(x->rank > y->rank){
y->p = x;
}else{
x->p = y;
if(x->rank == y -> rank){
y -> rank += 1;
}
}
}
void outer_union_sets(set*x, set*y){
link_set(outer_find_sets(x), outer_find_sets(y));
}
struct union_find_sets{
int count;
// 应该这么使用智能指针,不用一个个的delete
vector<shared_ptr<set>> sets_ptr;
union_find_sets(int n): count(n){
for(int i = 0; i < n; i++){
sets_ptr.push_back(make_shared<set>());
}
}
union_find_sets():count(0) { } // 什么也不做最好,这样就可以保证每次想要最想要的数字
void union_sets(int x ,int y){
if(find_sets(x) != find_sets(y)){
outer_union_sets(sets_ptr[x].get(), sets_ptr[y].get());
count--;
}
}
set* find_sets(int x){
return outer_find_sets(sets_ptr[x].get());
}
int get_count(){
return count;
}
void push_set(int x){
sets_ptr.push_back(make_shared<set>(x));
count++;
}
set* get(int x){
return sets_ptr[x].get();
}
};
并查集的许多应用之一就是确定无向图的连通分量,这里给出伪码描述:
CONNECTED-COMPONENT(G)
for each vextex属于G.V
meke_set(v)
for each edge(u,v)属于G.E
if find_set(u)!=find_set(v)
union(u,v)
判断两个节点是否在一个连通分量里面
SAME-COMPONNENT(u,v)
if(find_set(u)!=find_set(v))
return true;
else
return false;
整个图中连通分量的个数是剩下来的集合个数,这个可以在具体的程序中实现,可以看我实现的两种办法,都能解决
花了好几天进行学习并查集并且学会使用具体的编程语言进行实现,算是收获很大,希望自己再接再厉,学会发现问题,解决问题。
原文:https://www.cnblogs.com/beyondcoder/p/14299130.html