并查集(UnionFind)

并查集和其他树形结构不一样,是由孩子指向父亲,它解决了一些连接问题,怎么才能确定两个点是否相连呢?并查集可以非常快的确定两个点是否连接。

并查集(UnionFind)

如何确定连个点是否连接呢?

并查集(UnionFind)
我们可以用一个数组表示,对于0到9每个不同的编号可以表示不同的对象,这里可以看作一个点,而编号对应的不同的元素可以表示不同的集合,其中[0,2,4,6,8]表示一个集合。这样就可以表示连接问题了,0和2就是表示相连接,因为它们在一个集合,0和1因不在一个集合所以不连接。

对于一组数据并查集主要支持两个动作:

  • isConnected(p,q):查询元素p和q是否在一个集合
  • unionElements(p,q):合并元素p和q的集合

Code

#pragma once

class UF {
private:
    virtual const int getSize() const noexcept = 0;

    virtual bool isConnected(int p, int q) = 0;

    virtual void unionElements(int p, int q) = 0;
};
#pragma once

#include "UF.h"
#include

class UnionFind1 : public UF {
private:
    int *id;
    int size;
public:
    UnionFind1(int capacity) {
        id = new int[capacity];
        size = capacity;
        for (int i = 0; i < size; ++i) {
            id[i] = i;  //初始化不同的元素表示不同的集合都不相连
        }
    }

    const int getSize() const noexcept {
        return size;
    }
    //返回p所在的集合
    int find(int p) {
        assert(p >= 0 && p < size);
        return id[p];
    }
    //判断是否相连
    bool isConnected(int p, int q) {
        return find(p) == find(q);
    }
    //合并集合
    void unionElements(int p, int q) {
        int pID = find(p);
        int qID = find(q);
        if (pID == qID) {
            return;
        }

        for (int i = 0; i < size; ++i) {
            if (id[i] == pID) {
                id[i] = qID;    //让两个集合都相同就行了
            }
        }
    }
};

优化unionElements

从代码中可以看到:

  • unionElements的时间复杂度是O(n)
  • isConnected的时间复杂度是O(1)

并查集(UnionFind)

将每个元素,看做是一个节点,每个节点指向它的父节点,而根节点指向自己。如果我们进行unionElements(4,3)操作,那么就是让4索引的元素为3。同在一个树下面就是同一个集合表示相连。

并查集(UnionFind)

Code

#pragma once

#include "UF.h"
#include

class UnionFind2 : public UF {
private:
    int *parent;
    int size;
public:
    UnionFind2(int capacity) {
        parent = new int[capacity];
        size = capacity;
        for (int i = 0; i < size; ++i) {
            parent[i] = i;
        }
    }

    const int getSize() const noexcept {
        return size;
    }

    int find(int p) {
        assert(p >= 0 && p < size);
        while (p != parent[p]) {
            p = parent[p];
        }
        return p;
    }

    bool isConnected(int p, int q) {
        return find(p) == find(q);
    }

    void unionElements(int p, int q) {
        int pRoot = find(p);
        int qRoot = find(q);

        if (pRoot == qRoot) {
            return;
        }
        parent[pRoot] = qRoot;
    }
};

基于size的优化

并查集(UnionFind)
由于对真正合并那两个元素所在树的形状没有做判断,很多时候会增加树的高度。

并查集(UnionFind)
优化方法:节点个数小的那个节点去指向节点个数多个那个根节点。

Code

#ifndef UNION_FIND_UNIONFIND3_H
#define UNION_FIND_UNIONFIND3_H

#include "UF.h"
#include

class UnionFind3 : public UF {
private:
    int *parent;
    int *sz;
    int size;
public:
    UnionFind3(int capacity) {
        parent = new int[capacity];
        sz = new int[capacity];
        size = capacity;
        for (int i = 0; i < size; ++i) {
            parent[i] = i;
            sz[i] = 1;
        }
    }

    int getSize() {
        return size;
    }

    int find(int p) {
        assert(p >= 0 && p < size);
        while (p != parent[p]) {
            p = parent[p];
        }
        return p;
    }

    bool isConnected(int p, int q) {
        return find(p) == find(q);
    }

    void unionElements(int p, int q) {
        int pRoot = find(p);
        int qRoot = find(q);

        if (pRoot == qRoot) {
            return;
        }

        if (sz[pRoot] < sz[qRoot]) {
            parent[pRoot] = qRoot;
            sz[qRoot] += sz[pRoot];
        } else {
            parent[qRoot] = pRoot;
            sz[pRoot] += sz[qRoot];
        }
    }
};
#endif //UNION_FIND_UNIONFIND3_H

基于rank的和优化

并查集(UnionFind)
如果基于size优化会增加树的高度
并查集(UnionFind)
如果基于rank的优化rank[i]表示根节点为i的树的高度

并查集(UnionFind)

Code

#ifndef UNION_FIND_UNIONFIND4_H
#define UNION_FIND_UNIONFIND4_H

#include "UF.h"
#include

class UnionFind4 : public UF {
private:
    int *parent;
    int *rank;
    int size;
public:
    UnionFind4(int capacity) {
        parent = new int[capacity];
        rank = new int[capacity];
        size = capacity;
        for (int i = 0; i < size; ++i) {
            parent[i] = i;
            rank[i] = 1;
        }
    }

    int getSize() {
        return size;
    }

    int find(int p) {
        assert(p >= 0 && p < size);
        while (p != parent[p]) {
            p = parent[p];
        }
        return p;
    }

    bool isConnected(int p, int q) {
        return find(p) == find(q);
    }

    void unionElements(int p, int q) {
        int pRoot = find(p);
        int qRoot = find(q);

        if (pRoot == qRoot) {
            return;
        }

        if (rank[pRoot] < rank[qRoot]) {
            parent[pRoot] = qRoot;
        } else if (rank[pRoot] > rank[qRoot]) {
            parent[qRoot] = pRoot;
        } else {
            parent[qRoot] = pRoot;
            rank[pRoot] += 1;
        }
    }
};
#endif //UNION_FIND_UNIONFIND4_H

路径压缩

并查集(UnionFind)

优化方法一

并查集(UnionFind)
并查集(UnionFind)

优化方法二

并查集(UnionFind)

Code

#pragma once

#include "UF.h"
#include

class UnionFind : public UF {
public:
    UnionFind(int cap) : size(cap) {
        parent = new int[size];
        rank = new int[size];
        for (int i = 0; i < size; ++i) {
            parent[i] = i;
            rank[i] = 1;
        }
    }

    ~UnionFind() noexcept {
        delete[] parent;
        parent = nullptr;
    }

    const int getSize() const noexcept override {
        return size;
    }

    //查询元素p和q是否在一个集合
    bool isConnected(int p, int q) override {
        return find(p) == find(q);
    }

    //合并元素p和q的集合
    void unionElements(int p, int q) override {
        int pRoot = find(p);
        int qRoot = find(q);
        if (pRoot == qRoot) {
            return;
        }
        //就把其中一个的根节点挂到另一个的根上
        if (rank[pRoot] < rank[qRoot]) {
            parent[pRoot] = qRoot;  //高度小的根节点指向高度大的根节点,从而减少树的高度,防止退化
        } else if (rank[qRoot] < rank[pRoot]) {
            parent[qRoot] = pRoot;
        } else {
            parent[qRoot] = pRoot;
            ++rank[pRoot];
        }
    }

private:
    //查找元素p对应的集合编号,O(h)复杂度, h为树的高度
    //根节点就是集合编号,且根节点指向自己,索引 p == parent[p]
    int find(int p) {
        assert(p >= 0 && p < size);
        while (p != parent[p]) {
            parent[p] = parent[parent[p]];  //路径压缩,让p这个节点指向它父亲的父亲
            p = parent[p];
        }
        return p;
    }
    //递归版路径压缩,让集合中所有节点指向根节点
    int recFind(int p) {
        assert(p >= 0 && p < size);
        if (p != parent[p]) {
            parent[p] = find(parent[p]);
        }
        return parent[p];
    }

private:
    int *parent;
    int *rank;
    int size;
};

Original: https://www.cnblogs.com/chengmf/p/16459506.html
Author: 放飞梦想C
Title: 并查集(UnionFind)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/711513/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Android:Jetpack之视图绑定——ViewBinding

    1.Jetpack简介 手机厂商还没卷完Android 12, Android 13就悄然声息地来了,距离Google 2008年9月22日发布Android 1.0,已过去13个…

    技术杂谈 2023年7月10日
    091
  • crash命令 —— foreach

    参考:https://crash-utility.github.io/help_pages/foreach.html 用法: 在所有的进程上执行命令 这里的命令支持如下: 命令 可…

    技术杂谈 2023年5月30日
    0100
  • Jquery封装的ajax的使用过程发生的问题

    Jquery封装的ajax的使用过程发生的问题 今天在做项目的时候使用到了ajax来完成项目前后端数据交互,在之后发现在前端没有数据显示,而后端数据确实存在,在多次检查代码之后,发…

    技术杂谈 2023年7月24日
    065
  • 云原生的概念

    云原生其实是一种思想,并不是一种工具,云原生更多的是一种泛化的东西,是一种思想观念,首先要有意识的去想云原生这种东西,其次,他是一种技术、流程和企业管理方法的集合,所谓的技术,k8…

    技术杂谈 2023年7月23日
    073
  • iPhone设置微信Callkit

    国内的苹果手机的微信接听没有提醒,主要原因是出于种种原因微信限制了Callkit。如果想要锁屏接听,像接电话一样。方式一是更换绑定境外的手机号。这里使用Google Voice的虚…

    技术杂谈 2023年6月1日
    0193
  • elasticsearch

    一、什么是Elasticsearch? Lucene是一套用于 全文检索和 搜寻的 开源程序库,由Apache软件基金会支持和提供 Lucene提供了一个简单却强大的应用程序接口(…

    技术杂谈 2023年7月10日
    052
  • Prometheus系统下vmware_exporter配置

    ​ 为了方便管理设备,搞起了Prometheus。今天从vmware_exporter开始,监控起来我的vmware vsphere集群。 参考链接: 终于完成了一小步,但是总归是…

    技术杂谈 2023年5月31日
    0101
  • 不止面试-JVM垃圾回收面试题详解

    第一部分:面试题 本次分享我们将尝试回答以下问题: GC 是什么? 为什么要有 GC? 简单说一下java的垃圾回收机制。 JVM的常见垃圾回收算法有哪些? 为什么要使用分代回收机…

    技术杂谈 2023年7月11日
    087
  • Oracle授权允许远程访问–Oracle配置允许远程连接

    WindowsServer安装Oracle11g 11.2.0.1.0 对应的 windows 版本配置远程连接下载安装这里不详述具体安装步骤了,具体操作可参考安装教程 : Ora…

    技术杂谈 2023年5月31日
    095
  • Java — 反射

    程序在运行中也可以获取类的变量和方法信息,并通过获取到的信息来创建对象。程序不必再编译期就完成确定,在运行期仍然可以扩展。 示例:学生类 public class Student …

    技术杂谈 2023年7月11日
    072
  • 通过rinetd实现端口转发来访问内网的服务

    通过rinetd实现端口转发来访问内网的服务 通过外网来访问内网的服务 需要有一台能够外网访问的机器做端口映射,通过数据包转发来实现外部访问阿里云的内网服务 做端口映射的方案有很多…

    技术杂谈 2023年5月31日
    065
  • 进程最后的遗言

    进程最后的遗言 前言 在本篇文章当中主要给大家介绍父子进程之间的关系,以及他们之间的交互以及可能造成的状态,帮助大家深入理解父子进程之间的关系,以及他们之间的交互。 僵尸进程和孤儿…

    技术杂谈 2023年7月23日
    080
  • 怎么有效解决“未能创建 SSL/TLS 安全通道”异常

    之前写了一个服务自动程序,程序会访问第三方的一个https接口,一直用的好好的,今天突然报错了,异常就发生在访问接口的地方,”请求被中止,未能创建 SSL/TLS 安全…

    技术杂谈 2023年5月31日
    0228
  • io.undertow.websockets.jsr.ServerWebSocketContainer cannot be cast to org.apache.tomcat.websocket.server.WsServerContainer

    Caused by: java.lang.ClassCastException: io.undertow.websockets.jsr.ServerWebSocketContain…

    技术杂谈 2023年5月30日
    076
  • CSS的简单了解

    Hello CSS- Original: https://www.cnblogs.com/41357wangsun/p/16528876.htmlAuthor: 叨叨不是刀刀Tit…

    技术杂谈 2023年6月21日
    090
  • ThreadLocal解决了什么问题

    小明所在的项目组(迭代组:一直在迭代的路上),经常会在已有接口的基础上开发一些小功能,并且前提是在保证现有用户的不受影响基础上迭代。功能迭代,在代码层面小明有1w种实现方法(吹牛的…

    技术杂谈 2023年7月25日
    066
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球