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

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



我们可以用一个数组表示,对于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<cassert> 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)

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

Code

#pragma once

#include "UF.h"
#include<cassert> 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的优化



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



优化方法:节点个数小的那个节点去指向节点个数多个那个根节点。

Code

#ifndef UNION_FIND_UNIONFIND3_H
#define UNION_FIND_UNIONFIND3_H #include "UF.h"
#include <cassert> 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的和优化



如果基于size优化会增加树的高度



如果基于rank的优化rank[i]表示根节点为i的树的高度

Code

#ifndef UNION_FIND_UNIONFIND4_H
#define UNION_FIND_UNIONFIND4_H #include "UF.h"
#include <cassert> 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

路径压缩

优化方法一



优化方法二

Code

#pragma once

#include "UF.h"
#include<cassert> 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;
};

并查集(UnionFind)的更多相关文章

  1. 并查集(union-find)算法

    动态连通性 . 假设程序读入一个整数对p q,如果所有已知的所有整数对都不能说明p和q是相连的,那么将这一整数对写到输出中,如果已知的数据可以说明p和q是相连的,那么程序忽略p q继续读入下一整数对. ...

  2. 并查集 Union-Find

    并查集能做什么? 1.连接两个对象; 2.查询两个对象是否在一个集合中,或者说两个对象是否是连接在一起的. 并查集有什么应用? 1. Percolation问题. 2. 无向图连通子图个数 3. 最近 ...

  3. 并查集(Union-Find)算法介绍

    原文链接:http://blog.csdn.net/dm_vincent/article/details/7655764 本文主要介绍解决动态连通性一类问题的一种算法,使用到了一种叫做并查集的数据结构 ...

  4. 数据结构之并查集Union-Find Sets

    1.  概述 并查集(Disjoint set或者Union-find set)是一种树型的数据结构,常用于处理一些不相交集合(Disjoint Sets)的合并及查询问题. 2.  基本操作 并查集 ...

  5. 并查集 (Union-Find Sets)及其应用

    定义 并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题.常常在使用中以森林来表示. 集就是让每个元素构成一个单元素的集合,也就是按一定顺序将属于同一组的 ...

  6. 【LeetCode】并查集 union-find(共16题)

    链接:https://leetcode.com/tag/union-find/ [128]Longest Consecutive Sequence  (2018年11月22日,开始解决hard题) 给 ...

  7. 数据结构《14》----并查集 Union-Find

    描述: 并查集是一种描述解决等价关系.能够方便地描述不相交的多个集合. 支持如下操作    1. 建立包含元素 x 的集合  MakeSet(x) 2. 查找给定元素所在的集合 Find(x), 返回 ...

  8. 并查集(union-find set)与Kruskal算法

    并查集 并查集处理的是集合之间的关系,即‘union' , 'find' .在这种数据类型中,N个不同元素被分成若干个组,每组是一个集合,这种集合叫做分离集合.并查集支持查找一个元素所属的集合和两个元 ...

  9. 并查集(Union-Find) 应用举例 --- 基础篇

    本文是作为上一篇文章 <并查集算法原理和改进> 的后续,焦点主要集中在一些并查集的应用上.材料主要是取自POJ,HDOJ上的一些算法练习题. 首先还是回顾和总结一下关于并查集的几个关键点: ...

  10. 并查集(union-find sets)

    一.并查集及其优化 - 并查集:由若干不相交集合组成,是一种简单但是很好用的数据结构,拥有优越的时空复杂性,一般用于处理一些不相交集合的查询和合并问题. - 三种操作: 1.Make_Set(x) 初 ...

随机推荐

  1. Java集合框架学习(十三) Collections类详解

    Collections类介绍 这个类操作或返回集合的专有静态方法. 它包含多态算法,利用wrappers(封装器)返回指定集合类型的新集合,以及其他一些零散功能. 如果该类的方法引用的集合或类对象为n ...

  2. Direct2D 旋转篇

    微软文档:Transforms 本篇通过官方文档学习,整理出来的demo,初始样本请先创建一个普通的desktop app. ID2D1SolidColorBrush* m_pOriginalShap ...

  3. [2023本地存储方案](https://www.cnblogs.com/fangchaoduan/p/17608006.html)

    2023本地存储方案 本地存储方案 cookie 本地存储:有期限的限制,可以自己设置过期期限.在期限内,不论页面刷新还是关闭,存储的信息都还会存在. localStorage 本地持久化存储:页面刷 ...

  4. Springboot集成Disruptor做内部消息队列

    一.基本介绍 Disruptor的github主页:https://github.com/LMAX-Exchange/disruptor 1,什么是 Disruptor? (1)Disruptor 是 ...

  5. 项目实战:Qt球机控制工具 v1.0.0(球机运动八个方向以及运动速度,设置运动到指定角度,查询当前水平和垂直角度)

    需求   1.调试球机控制,方向速度,设置到指定的角度:  2.支持串口,485等基于串口的协议端口配置打开:  3.子线程串口控制和.子线程协议解析:  4.支持球机水平运动速度.垂直运动速度设置: ...

  6. 死锁,互斥锁,递归锁,线程事件Event,线程队列Queue,进程池和线程池,回调函数,协程的使用,协程的例子---day33

    1.死锁,互斥锁,递归锁 # ### 死锁 互斥锁 递归锁 from threading import Lock,Thread,RLock #递归锁 import time noddle_lock = ...

  7. 【Azure 环境】各种语言版本或命令,发送HTTP/HTTPS的请求合集

    问题描述 写代码的过程中,时常遇见要通过代码请求其他HTTP,HTTPS的情况,以下是收集各种语言的请求发送,需要使用的代码或命令 一:PowerShell Invoke-WebRequest htt ...

  8. 【Azure 应用服务】Web App Service 中的 应用程序配置(Application Setting) 怎么获取key vault中的值

    问题描述 App Service中,如何通过 Application Setting 来配置 Key Vault中的值呢? 问题解答 首先,App Service服务可以直接通过引用的方式,无需代码的 ...

  9. 浅入ABP(2):添加基础集成服务

    浅入ABP(2):添加基础集成服务 版权护体作者:痴者工良,微信公众号转载文章需要 <NCC开源社区>同意. 目录 浅入ABP(2):添加基础集成服务 定义一个特性标记 全局统一消息格式 ...

  10. Jupyter Notebook 遇上 NebulaGraph,可视化探索图数据库

    在之前的<手把手教你用 NebulaGraph AI 全家桶跑图算法>中,除了介绍了 ngai 这个小工具之外,还提到了一件事有了 Jupyter Notebook 插件: https:/ ...