0101quick_find_union-union_find-动态连通性-算法研究

news2025/1/18 16:49:49

文章目录

    • 1 前言
    • 2 动态连通性
    • 3 算法
      • 3.1 算法设计
      • 3.1 union-find算法API
      • 3.2 数据结构和通用实现
      • 3.3 quick-find算法
        • 3.3.1 思想和实现
        • 3.3.2 分析
      • 3.4 quick-union算法
        • 3.4.1 算法描述
        • 3.4.2 算法实现
        • 3.4.3 性能分析
    • 结语

1 前言

为了说明我们设计和分析算法的基本方法,我们现在来学习一个具体的例子。我们的目的是强调以下几点:

  • 优秀的算法因为能够解决实际的问题而变得更为重要;
  • 高效算法的代码也可以很简单;
  • 理解某个实现的性能特点是一项有趣而令人满足的挑战;
  • 在解决统一问题的多种算法之间进行选择时,科学方法是一种重要的工具;
  • 迭代式改进能够让算法的效率越来越高。

下面我们要研究的就是关于动态连通性的计算性问题,首先我们会给出一个简单的方案,然后对它的性能进行研究并由此得出应用如果继续改进我们的算法。

2 动态连通性

有以下问题:问题的输入是一列整数对,其中某个整数表示某个类型的对象,一对整数p,q表示“p和q是相连的”。相连时一种等价关系,具有以下性质:

  • 自反性:p和p也是相连的;
  • 对称性:如果p和q时相连的,那么q和p也是相连的;
  • 传递性:如果p和q时相连的,q和r时相连的,那么p和r也是相连的。

依据等价关系分类,可以把对象分为若干个等价类。我们的目标是编写程序来过滤掉掉没有意义的整数对(两个整数在同一个等价类中)。

问题处理描述:

  • 程序从输入中读取了整数对pq时,如果已知所有整数对都不能说明p和q时相连的,那么将该整数的写入输出中;
  • 如果已知整数对能说明p和q上相连的,那么程序忽略pq这对整数继续处理下一对整数。

为了达到上述效果,我们需要一个数据结构存储已知的整数足够多的信息,并用它们判断一对新对象是否相连。 这种问题我们称之为动态连通性问题。动态连通性应用,如下表所示:

应用场景对象对象关系(整数对)问题描述
大型计算机网络计算机网络中连接计算机与计算机之间是否需要假设一条新的连接才能通信
电路板触点连接触点之间的电路触点之间是否电路已经连通
变量名等价性变量名同一个对象的多个引用判断两个变量名是否等价(指向同一个对象的引用)

在以后内容中我们使用网络中的术语,称对象为触点,将整数对称为连接,将等价类称为连通分量或者分量。简单起见,用0到N-1之间到整数表示N个触点。

如下图2-1所示,有几个连通分量?怎么判断两个触点是否在同一个分量中呢?

在这里插入图片描述

3 算法

3.1 算法设计

**我们设计算法的第一个任务就是要精确的定义问题。**一般情况下,算法能解决的问题越大,它完成任务所需的时间和空间就越多。但是它们之间的量化关系很难预先知道。通常我们只会在发现解决问题很困难,或者代价太大,或者幸运地发现算法所提供的信息比原问题所需的更加有用是修改问题。

以连通性问题为例,问题只要求我们能够判断给定的整数对p和q是否相连,但并没有要求给出两者之间的通路上的所有连接。

为了说明这个问题,我们设计类一份API封装所需的基本操作:初始化、连接两个触点、判断包含某个触点的分量、判断两个触点是否在同一个分量中以及返回分量的数量。

3.1 union-find算法API

详细的API如下表3.1-1所示:

public classUF
publicUF(int N)初始化N个触点
public voidunion(int p, int q)连接触点p和q
public intfind(int p)p所在分量的标志符
public booleanconnected(int p, int q)触点p和q是否相连
public intcount()连通分量的数量
  • 如果两个触点在不同的连通分量中,union()方法会将两个分量合并;
  • count()返回连通分量的数量,初始为N,没合并一次,数量减1。

3.2 数据结构和通用实现

为解决动态连通性问题设计算法的任务转化为实现这份API,所有的实现都应该:

  • 定义一种数据结构表示已知的连接;
  • 基于此数据结构实现高效的union()、find()、connected()和count()方法。

**数据结构的性质直接影响算法的效率。**这里我们触点和分量用int值表示,我们用一个以触点为索引的数组id[]作为数据结构表示所有的分量。

  • 初始化:触点i的名称为分量的标识,分量初始大小为N,值为触点对应的索引id[i];
  • connected():通过find§==find(q)判断触点q和触点q所在分量是否相等即是否相连。

union-find的成本模型:在研究实现union-find的APi的各种算法时,我们统计的是数组的访问次数(访问任意数组元素的次数,无论读写)。

3.3 quick-find算法

3.3.1 思想和实现

一种思想是保证当前仅当ip[p]等于id[q]时p和q时连通的。即同一连通分量中所有的id[]的值都是相等的。

  • find(i):只需返回id[i]即为触点i所在的分量标志;
  • union():
    • 首先通过connected()判断触点p和q是否相连,如果相连,啥也不做;
    • 如果不相连,连接触点p和触点q所在的分量。
      • 触点p所在分量标志都为一个值,触发q所在的连通分量都为另外一个值;
      • 通过遍历把p所在连通分量值改为q所在连通分量的值或者把q所在连通分量id[q]改为p所在分量的值id[p]

我们称这种实现方式为quic-find算法,实现代码3.3-1如下所示:

package edu.princeton.cs.algs4;

/**
 *  动态连通性quick-find算法
 */

public class QuickFindUF {
    /**
    * 触点所在分量标志
    */
    private int[] id;
  
    /**
    * 连通分量数量
    */
    private int count;

    /**
     * 初始化触点数量
     * {@code n} elements {@code 0} through {@code n-1}. 
     *
     * @param  初始化触点数量{@code n}
     */
    public QuickFindUF(int n) {
        count = n;
        id = new int[n];
        for (int i = 0; i < n; i++)
            id[i] = i;
    }

    /**
     * 连通分量的数量
     *
     * @return 数量 (between {@code 1} and {@code n})
     */
    public int count() {
        return count;
    }
  
    /**
     * 返回触点p所在的分量标志
     *
     * @param  触点p 
     * @return {@code p}所在分量的标志
     */
    public int find(int p) {
        validate(p);
        return id[p];
    }

    /**
    * 校验触点p是否合法
    * @param  触点p 
    */
    private void validate(int p) {
        int n = id.length;
        if (p < 0 || p >= n) {
            throw new IllegalArgumentException("index " + p + " is not between 0 and " + (n-1));
        }
    }

    /**
     * 判断触点p和触点q是否相连
     * 
     * @param  触点p
     * @param  触点q
      * @return {@code true} 如果 {@code p} 和 {@code q} 相连;
     *         {@code false} 否则
     */
    @Deprecated  
    /**
     * 连接触点p所在的分量和触点q所在的分量
     */
    public void union(int p, int q) {
        validate(p);
        validate(q);
        int pID = id[p];   // needed for correctness
        int qID = id[q];   // to reduce the number of array accesses

        // p and q are already in the same component
        if (pID == qID) return;

        for (int i = 0; i < id.length; i++)
            if (id[i] == pID) id[i] = qID;
        count--;
    }
}

测试数据如下所示:

10
4 3
3 8
6 5
9 4
2 1
8 9
5 0
7 2
6 1
1 0
6 7

测试程序3.3-2如下所示:

public static void testQF() {

    String path = System.getProperty("user.dir") + File.separator + "asserts/tinyUF.txt";
    In in = new In(path);
    int n = in.readInt();
    QuickFindUF uf = new QuickFindUF(n);
    while (in.hasNextLine()) {
        int p = in.readInt();
        int q = in.readInt();
        if (uf.find(p) == uf.find(q)) continue;
        uf.union(p, q);
        StdOut.println(p + " " + q);
    }
    StdOut.println(uf.count() + " 连通分量");
}

测试结果如下所示:

4 3
3 8
6 5
9 4
2 1
5 0
7 2
6 1
2 连通分量

轨迹示意图3.3-1如下所示:

在这里插入图片描述

3.3.2 分析

find()操作的速度很快,因为它只需要访问id[]数组一次。但quick-find算法一般无法处理大型问题,因为对于每一对输入uinon()都需要扫描整个id[]数组。

命题F。在quick-find算法中,每次find()调用只需要访问数组一次,而归并两个分量的union()操作访问数组的次数在N到2N+2次之间;

证明:find()访问数组一次很明显。union()获取pid,qid访问2次id[]。如果全部pid等于qid那么最少需要遍历整个数组即N次;如果pid和qid都不想等,那么会检查id[]数组中的全部N个元素并改变它们中的1-N个元素的值,即最多2N+2次。

在最坏情况下,我们使用quick-find算法来解决动态连通性问题并且最坏只得到了一个连通分量,那么至少需要调用N-1次union(),即至少N(N-1)~(2N+2)(N-1)次数组访问-qucik-find解决动态连通性问题算法是平方级别的。

3.4 quick-union算法

3.4.1 算法描述

即然qucik-find算法的瓶颈在于union连接,那么我们想办法提高union方法的速度。

quick-uinon算法使用相同的数据结构:

  • 以触点为索引的数组id[];
  • 但是某个触点p,对应的id[p]不在表示分量标志,而是指向同一分量中的另外一个触点-这种联系我们称之为链接。

find()方法实现:

  • 我们从给定的触点p触发,通过调用id[p]获取上一个触点,以此类推,直到该分量的根触点,即链接指向自己的触点-在后面我们要学习的树结构中称之为跟结点。

connected()方法实现:

  • 只有当2个触点对应的根触点相等时,表示2个触点在同一个连通分量中。

union(p,q)方法实现:

  • 我们有p和q的链接分别找到它们的根触点,判断如果想等,不需要操作;
  • 不相等只需要将以恶根触点链接到另外一个根触点即可。

3.4.2 算法实现

实现代码3.4.2-1如下所示:

package edu.princeton.cs.algs4;

/**
 *  quick-union算法
 */
public class QuickUnionUF {
    /**
     * 父链接(触点)数组
    */ 
    private int[] parent;
  
    /**
     * 分量数量
    */
    private int count;

    /**
     * 初始化有n个触点的parent[]
     *
     * @param  n the number of element
     */
    public QuickUnionUF(int n) {
        parent = new int[n];
        count = n;
        for (int i = 0; i < n; i++) {
            parent[i] = i;
        }
    }

    /**
     * 分量的数量
     *
     * @return 分量的数量
     */
    public int count() {
        return count;
    }
  
    /**
     * 返回触点p所在的分量标志
     *
     * @param  p 触点p
     */
    public int find(int p) {
        validate(p);
        while (p != parent[p])
            p = parent[p];
        return p;
    }

    /**
     * 校验触点p
    */
    private void validate(int p) {
        int n = parent.length;
        if (p < 0 || p >= n) {
            throw new IllegalArgumentException("index " + p + " is not between 0 and " + (n-1));
        }
    }

    /**
     * 判断触点p和触点q是否在同一分量中
     * 
     * @param  p 触点p
     * @param  q 触点q
     * @return {@code true} 如果触点 {@code p} and {@code q} 在同一分量中;
     *         {@code false} 否则
     */
    @Deprecated
    public boolean connected(int p, int q) {
        return find(p) == find(q);
    }

    /**
     * 合并触点p所在分量和触点q所在分量
     *
     * @param  p 触点或者所在分量
     * @param  q 触点q所在分量
     */
    public void union(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        if (rootP == rootQ) return;
        parent[rootP] = rootQ; 
        count--;
    }
}

测试用例同quick-find算法,测试代码3.4.2-2如下所示:

public static void testQU() {
    String path = System.getProperty("user.dir") + File.separator + "asserts/tinyUF.txt";
    In in = new In(path);
    int n = in.readInt();
    QuickUnionUF uf = new QuickUnionUF(n);
    while (!in.isEmpty()) {
        int p = in.readInt();
        int q = in.readInt();
        if (uf.find(p) == uf.find(q)) continue;
        uf.union(p, q);
        StdOut.println(p + " " + q);
    }
    StdOut.println(uf.count() + " components");
}

测试结果如下所示:

4 3
3 8
6 5
9 4
2 1
5 0
7 2
6 1
2 components

3.4.3 性能分析

id[]用父链接的形式表示了一片森林,union实现了将一个根结点变为另外一个根结点的父结点,从而归并了两棵树。如下图3.4.3-1所示:

在这里插入图片描述

定义:一棵树的大小树它的节点数量。树中的一个节点的深度树它到根结点路径上的链接数。树的高度树它的所有节点中的最大深度值。

命题G。quick-union算法中的find()方法访问数组的次数为1加上给定触点对应的节点的深度2倍。union()和connected()访问数组的次数为两次find()操作给定两个触点的分别存在不同树中则还需要加1。

假设我们使用quick-union算法最终解决了动态连通性问题并最终只得到一个分量,由命题G只算法在最坏情况下上平方级别的。

最坏情况即我们的整数对为有序的0-1、0-2、0-3等,最后我们所有的触点全都在一个分量中,id[]形成一条链表。整数对0-i,union()操作访问数组次数2i+1(触点0的深度i-1,触点i的深度0),处理N对整数所需所有find()操作数组的总次数 3 + 5 + 7 + ⋯ + ( 2 N − 1 ) ∽ N 2 3+5+7+\cdots+(2N-1)\backsim N^2 3+5+7++(2N1)N2

结语

如果小伙伴什么问题或者指教,欢迎交流。

❓QQ:806797785

⭐️源代码仓库地址:https://gitee.com/gaogzhen/algorithm

参考链接:

[1][美]Robert Sedgewich,[美]Kevin Wayne著;谢路云译.算法:第4版[M].北京:人民邮电出版社,2012.10.p136-149.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/428046.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

通过SSD对齐功能轻松将 HDD 克隆到 SSD

由于 SSD 的优越性能&#xff0c;越来越多的用户正在考虑将他们的操作系统从 HDD 转移到 SSD。然而&#xff0c;一些用户反馈&#xff0c;在迁移之后&#xff0c;SSD 的启动速度并未提升。 为什么将操作系统从 HDD &#xff08;机械硬盘&#xff09;迁移到 SSD&#xff08;固态…

【leetcode hot 100】【7】11. 盛最多水的容器

题目 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明&#xff1a;你不能倾斜容器。 示…

JUC-多线程(11.面试问题简析)学习笔记

文章目录1. synchronized 关键字底层原理以及其与 lock 的区别2. 对CAS的理解以及底层实现原理3. ConcurrentHashMap实现线程安全的底层原理是什么4.对JDK中的AQS了解吗&#xff1f;AQS的实现原理是什么&#xff1f;5. 线程池的核心配置参数是干什么的&#xff1f;应该怎么用&a…

电子表格软件与一站式BI的区别

看完本节内容&#xff0c;相信您能够了解到电子表格软件&#xff08;代号电子表格软件&#xff09;与「一站式 BI」的主要区别。所谓一站式BI在官网上的名称就是Smartbi V10.5&#xff0c;代号就是Smartbi一直在使用insight。 这两个产品都属于商业智能BI软件的品类&#xff0…

文献阅读:Semantic Communications: Principles and Challenges

传统通信和语义通信的对比 语义信道容量&#xff08;从公式角度解释为什么语义通信的信道容量可以高于传统通信的信道容量&#xff09; 离散无记忆信道的语义信道容量表示为&#xff1a; Cssup⁡p(Z∣X){I(X;V)−H(Z∣X)HS(V)‾}C_s\sup _{p(Z \mid X)}\left\{I(X ; V)-H(Z \m…

再识华为云数据库——GaussDB

目录 一、GaussDB: 立足创新与自研&#xff0c;助力企业核心数据安全高效上云 二、GaussDB: 基于华为openGauss开放生态打造的 金融级分布式数据库 三、GaussDB(for MySQL): 基于开源生态打造的企业级自研云原生数据库 四、DRSUGO&#xff1a;数据库结构应用数据一站式迁移…

Vue学习——【第四弹】

前言 上一篇文章 Vue学习——【第三弹】 中我们了解了MVVM模型&#xff0c;这篇文章接着学习Vue中的数据代理。 简单介绍 数据代理就是**一个对象(A)来代理对另一个对象(B)的属性操作(A一定要包含B)。**直接看定义大家可能觉得有些抽象&#xff0c;我们可以用代码来实现。 …

网络编程

网络编程 1.1、概述 计算机网络&#xff1a; 计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备&#xff0c;通过通信线路连接起来&#xff0c;在网络操作系统&#xff0c;网络管理软件及网络通信协议的管理和协调下&#xff0c;实现资源共享和信息传递的计…

第十一节 java中的线程类

java中常见类&#xff1a;字符串类 集合类 异常类 前面讲完了 程序&#xff1a;是含有指令和数据的文件&#xff0c; 被存储在磁盘或其他的数据存储 设备中&#xff0c;也就是说程序是静态的 代码。.c .java文件都放在外存里 获得资源获得CPU执行 这叫进程 程序和进程的…

【学习积累】Queue 与 ConcurrentQueue性能测试

在 C# 中&#xff0c;关于队列&#xff08;Queue&#xff09;有两种&#xff0c;一种就是我们普通使用的队列&#xff0c;另一种是线程安全的队列 ConcurrentQueue<T> 。 ConcurrentQueue表示线程安全的先进先出 (FIFO) 集合。https://learn.microsoft.com/zh-cn/dotnet…

【创作赢红包】python学习——【第七弹】

前言 上一篇文章 python学习——【第六弹】中介绍了 python中的字典操作&#xff0c;这篇文章接着学习python中的可变序列 集合 集合 1&#xff1a; 集合是python语言提供的内置数据结构&#xff0c;具有无序性&#xff08;集合中的元素无法通过索引下标访问&#xff0c;并且…

Qt框架概述

Qt框架概述一、什么是Qt二、了解QtCreator三、创建Qt项目*Qt项目框架及文件介绍四、设置窗口属性五、按钮创建按钮方式一按钮属性设置创建按钮方式二六、对象模型一、什么是Qt 概念&#xff1a; Qt是一个基于C的 跨平台的图形用户界面应用程序框架。 常见GUI Qt &#xff1a;…

状态错误 MSB8040,此项目需要缓解了 Spectre 漏洞的库。从 Visual Studio 安装程序(单个组件选项卡)为正在使用的任何工具集和体

“Spectre Mitigation”缓解错误 如果出现“Spectre Mitigation”这种错误&#xff0c;就要了解下PIPE技术&#xff1a;流水线技术&#xff0c;比如3级流水线&#xff0c;避免CPU空闲&#xff0c;不浪费时间&#xff0c;但是前提是没有跳转&#xff0c;指令都是顺序执行的&…

pytorch transforms图像增强

一、前言 在学习自己的项目发现自己有很多基础知识不牢&#xff0c;对于图像处理有点不太清楚&#xff0c;因此写下来作为自己的笔记&#xff0c;主要是我想自己动手写一下每一句代码到底做了什么&#xff0c;而不是单纯的我看了知道了它做了什么&#xff0c;说白了&#xff0c…

【Maven】开发自己的starter依赖

【Maven】开发自己的starter依赖 文章目录【Maven】开发自己的starter依赖1. 准备工作1.1 创建一个项目1.2 修改pom文件1.3 修改项目结构2. 动手实现2.1 创建客户端类2.2 创建配置类2.3 配置路径2.4 下载到本地仓库3. 测试1. 准备工作 1.1 创建一个项目 打开idea&#xff0c;…

BP神经网络原来就是曲线拟合

本站原创文章&#xff0c;转载请说明来自《老饼讲解-BP神经网络》bp.bbbdata.com 在初学BP神经网络的时候&#xff0c;总是非常抽象和难理解 但是&#xff0c;学久了会发现&#xff0c;BP神经网络原来就是曲线拟合&#xff01; 一下子才具体、深入的理解到BP神经网络是什么 本文…

字节,腾讯过来的面试自动化测试就这水平吗?鬼知道经历了什么?

本人12年从业经验&#xff0c;曾就职于美团测试开发框架组&#xff0c;搭建过美团platuo测试框架&#xff0c;thrift测试框架&#xff0c;自动化测试平台&#xff0c;熟悉python3&#xff0c;java&#xff0c;vue&#xff0c;在多家公司从0到1搭建过自动化测试框架&#xff0c;…

linux文件编辑--vi

目录标题vi/vim中三种模式命令模式下的常用命令--光标移动输入模式末行模式vim中常用的操作类型命令行模式下的常用命令--复制、粘贴、删除命令模式下的常用命令--文件内容查找命令模式中的基本操作--撤销编辑及保存退出末行模式中的基本操作--保存文件内容及退出vi编辑器末行模…

GitHub标星15w,如何用Python实现所有算法?

学会了 Python 基础知识&#xff0c;想进阶一下&#xff0c;那就来点算法吧&#xff01;毕竟编程语言只是工具&#xff0c;结构算法才是灵魂。 新手如何入门 Python 算法&#xff1f; 几位印度小哥在 GitHub 上建了一个各种 Python 算法的新手入门大全。从原理到代码&#xf…

[论文阅读RGBD-SOD][2022_TCSVT_MoADNet][轻量化]

MoADNet: Mobile Asymmetric Dual-Stream Networks for Real-Time and Lightweight RGB-D Salient Object Detection paper&#xff1a;https://ieeexplore.ieee.org/abstract/document/9789193 动机 尽管已有许多优秀的RGB-D SOD技术被提出&#xff0c;但它们大多关注性能…