并查集及其应用

并查集是一种高级数据结构,在课堂上学习老师并不会讲到,这次刷Leetcode,发现自己还没有涉及到并查集相关内容,于是经过学习《算法导论》,进一步了解了并查集的相关内容,并且手写了并查集的模板,中间遇到了许多问题,例如内存分配的问题,内存防止泄露的问题,最后一步一步解决问题,不仅学到了算法,而且对c++智能指针有了进一步认识并且能够使用智能指针解决问题,因此在这里汇总一下。

并查集有关知识

并查集是一种用于不相交集合数据结构,它维护了一个不相交动态的集合\(\xi=\begin{Bmatrix}s1,s2,...,sk\end{Bmatrix}\).我们在其中使用一个代表来表示每一个集合,它是这个集合的某个成员。(这边一定要注意对代表的认识)并查集一共有三个基本操作:

  • make_set(x):建立一个新的集合,它的唯一成员(也就是代表)是x。因为各个集合是不相交的,故x不会出现在别的某个集合中。
  • union(x, y):将包含x和y的两个动态集合\(S_x\)\(S_y\)进行合并,成为一个新的集合。虽然union操作特别得选择\(S_x\)\(S_y\)中的代表作为新的代表。然而结果集的代表可以是\(S_x\)\(S_y\)的任何成员
  • find-set(x):返回一个指针,这个指针指向包含x的(唯一)集合的代表

并查集的最简单实现

#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;

整个图中连通分量的个数是剩下来的集合个数,这个可以在具体的程序中实现,可以看我实现的两种办法,都能解决

总结

花了好几天进行学习并查集并且学会使用具体的编程语言进行实现,算是收获很大,希望自己再接再厉,学会发现问题,解决问题。

posted @ 2021-01-19 17:39  Beyondcoder  阅读(280)  评论(0编辑  收藏  举报