0102加权quick_union和路径压缩-union-find-动态连通性-算法研究

news2024/11/8 20:54:02

3 union-find算法

3.5 加权quick-union算法

3.5.1 算法实现

quick-union出现最坏情况,因为我们是随意将一棵树链接到另外一棵树上,修改如下:

  • 添加一个数组和一些代码记录树中节点数;
  • 链接时将节点数较小的树链接到较大的树上,示意图3.5.1-1如下所示:

在这里插入图片描述

这样能大大改进算法的效率,我们将它称为加权quick-union算法。

基于union-find各实现算法的相同性,遵循依赖倒置原则,我们设计一个同一的接口,抽象相同的方法,一个抽象实现类,实现相同的方法。

UnionFind接口代码如下3.5.1所示:

package com.gaogzhen.algorithms4.foundation;

/**
 * union-find算法功能接口
 */
public interface UnionFind {

    /**
     * 查找触点p所在分量标志
     * @param p 触点p
     * @return 分量标志
     */
    int find(int p);

    /**
     * 触点p和触点q是否在同一分量内
     * @param p 触点p
     * @param q 触点q
     * @return {@code true}如果触点{@code p}和触点{@code q}在同一分量内;{@code false}否则
     */
    boolean connected(int p, int q);

    /**
     * 合并触点p和触点q
     * @param p 触点q
     * @param q 触点q
     */
    void union(int p, int q);


    /**
     * 分量数量
     * @return 分量数量
     */
    int count();
}

抽象类AbstractUnionFind代码3.5-2如下所示(有待进一步完善):

package com.gaogzhen.algorithms4.foundation;

/**
 * union-find 默认实现
 */
public abstract class AbstractUnionFind implements UnionFind{

    /**
     * 触点所在分量标志
     */
    private int[] id;

    /**
     * 连通分量数量
     */
    private int count;


    /**
     * 初始化数组id
     * @param n 数组长度
     */
    public AbstractUnionFind(int n) {
        count = n;
        id = new int[n];
        for (int i = 0; i < n; i++)
            id[i] = i;
    }


    /**
     * 获取触点i对应的分量值
     * @param index 触点i
     * @return  触点对应的分量标志
     */
    protected int getId(int index) {
        return id[index];
    }

    /**
     * 设置触点i对应的分量值
     * @param index 触点index
     * @param val 分量值
     */
    protected void setId(int index, int val) {
        this.id[index] = val;
    }

    /**
     * 数组id长度
     * @return 数组id长度
     */
    protected int capacity() {
        return id.length;
    }


    /**
     * 分量数量减1
     */
    protected void decreaseCount() {
        count--;
    }

    /**
     * 连通分量的数量
     *
     * @return 数量 (between {@code 1} and {@code n})
     */
    @Override
    public int count() {
        return count;
    }

    /**
     * 校验触点p是否合法
     * @param  p 触点p
     */

    protected 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 触点p
     * @param  q 触点q
     * @return {@code true} 如果 {@code p} 和 {@code q} 相连;
     *         {@code false} 否则
     */
    @Deprecated
    @Override
    public boolean connected(int p, int q) {
        validate(p);
        validate(q);
        return find(p) == find(q);
    }
}

加权qucik-union算法实现代码如下3.5-3所示:

package com.gaogzhen.algorithms4.foundation;

/**
 *  加权quick-union
 */
public class WeightedQuickUnionUF extends AbstractUnionFind{

    /**
     * 当前触点为根节点树中节点数量
     */
    private int[] size;


    /**
     * 初始化n个触点的数组
     *
     * @param  n 数量n
     */
    public WeightedQuickUnionUF(int n) {
        super(n);
        size = new int[n];
        for (int i = 0; i < n; i++) {
            size[i] = 1;
        }
    }



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

    /**
     * 合并触点p和触点q所在的分量
     *
     * @param  p 触点p所在分量
     * @param  q 触点p所在分量
     */
    public void union(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        if (rootP == rootQ) return;

        // make smaller root point to larger one
        if (size[rootP] < size[rootQ]) {
            setId(rootP, rootQ);
            size[rootQ] += size[rootP];
        }
        else {
            setId(rootQ, rootP);
            size[rootP] += size[rootQ];
        }
        decreaseCount();
    }
}

测试代码做相应调整3.5-4如下所示:

public static void testWeightedUF() {
    In in = fetchData();
    int n = in.readInt();
    testUF(new WeightedQuickUnionUF(n), in);
}

private static In fetchData() {
    String path = System.getProperty("user.dir") + File.separator + "asserts/tinyUF.txt";
    return new In(path);
}

private static void testUF(UnionFind uf, In in) {
    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.5.2 算法性能分析

命题H。对于N个触点,加权quick-union算法构造的森林中的任意节点的深度最多为 lg ⁡ N \lg N lgN

证明:数学归纳法证明一个更强的命题,即森林中大小为k的树的高度最大为 lg ⁡ k \lg k lgk

证明:当 k 的高度为 1 时,树的高度为 0 根据归纳法,假设大小为 i 的树的高度最多为 lg ⁡ i , i ≤ k 设 i ≤ j 且 i + j = 看,当我们将大小为 i 和大小为 j 的树归并时,小树的高度 + 1 ,即 i + lg ⁡ i = lg ⁡ ( 2 i ) ≤ lg ⁡ ( i + j ) = lg ⁡ k 证明:当k的高度为1时,树的高度为0\\ 根据归纳法,假设大小为i的树的高度最多为\lg i,i\le k\\ 设i\le j且i+j=看,当我们将大小为i和大小为j的树归并时,小树的高度+1,即\\ i+\lg i=\lg(2i)\le\lg(i+j)=\lg k 证明:当k的高度为1时,树的高度为0根据归纳法,假设大小为i的树的高度最多为lgi,ikiji+j=看,当我们将大小为i和大小为j的树归并时,小树的高度+1,即i+lgi=lg(2i)lg(i+j)=lgk

推论。对于加权quick-union算法和N个触点,在最坏情况下find()、connected()和union()的成本低增长数量级为 lg ⁡ N \lg N lgN

证明。在森林中,对于从一个节点到它根节点到路径上的每个节点,每种操作最多都只会访问数组常数次。

加权quick-union算法处理N个触点和M条连接时最多访问数组 c M lg ⁡ N 次, c 为常数 cM\lg N次,c为常数 cMlgN次,c为常数。而quick-find则至少需要访问NM次。因此,加权quick-unino算法能让我们在合理的时间内解决实际中大规模动态连通性问题。

3.6 路径压缩

理想情况下 ,我们希望每个节点都直接链接到它的根节点上。

路径压缩算法其中一种实现代码3.6-1如下所示:

package com.gaogzhen.algorithms4.foundation;


/**
 *
 */

public class UF extends AbstractUnionFind{

    /**
     * 触点为根节点树高度
     */
    private byte[] rank;


    /**
     * 初始化
     *
     * @param  n 数量
     */
    public UF(int n) {
        super(n);
        rank = new byte[n];
        for (int i = 0; i < n; i++) {
            rank[i] = 0;
        }
    }

    /**
     * 返回触点p所在分量的树的根节点
     *
     * @param  p 节点p
     * @return 节点p所在树根节点
     */
    public int find(int p) {
        validate(p);
        while (p != getId(p)) {
            // 路径减半压缩,当前节点指向其祖父节点
            setId(p , getId(getId(p)));
            p =  getId(p);
        }
        return p;
    }
    /**
     * 归并触点p和触点q
     *
     * @param  p 触点p
     * @param  q 触点q
     */
    public void union(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        if (rootP == rootQ) return;

        // 较低高度的树链接到较高高度的树
        if      (rank[rootP] < rank[rootQ]) setId(rootP, rootQ);
        else if (rank[rootP] > rank[rootQ]) setId(rootQ, rootP);
        else {
            // 高度相同,一个链接到另外一个,被链接的高度+1
            setId(rootQ, rootP);
            rank[rootP]++;
        }
        decreaseCount();
    }
}
  • find()方法:通过循环,将路径上遇到的所有节点直接链接到根节点;

测试代码3.6-2如下所示:

public static void testPathCompress() {
    In in = fetchData();
    int n = in.readInt();
    testUF(new UF(n), in);
}

fetchData()和testUF()代码同上,测试结果:

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

3.7 分析

路径压缩的加权quick-union算是目前最优的算法,但并非所有操作都能在常数时间内完成。使用路径压缩的加权quick-union算法的每个操作在最坏情况下(均摊后)都不是常数级别。

各种union-find算法上性能特点如下表3.7-1所示:

算法N 个触点时成本增长数量级(最坏情况下)
构造函数union()find()
quick-findNN1
quick-unionN树的高度树的高度
加权quick-unionN lg ⁡ N \lg N lgN lg ⁡ N \lg N lgN
路径压缩加权quick-unionN接近1均摊成本
理想情况N11

我们能找到一种能够保证在常数时间内完成各种操作的算法吗?

4 展望

通过对每种UF算法实现都改进了上一个版本的实现,但这个过程并不突兀。

  • 解决方法的实现很简单,可以用经验性的数据评估各个算法的优劣;
  • 可以通过这些研究验证将算法性能量化的数学结论。

以后研究各种基础问题时,我们都会遵循类似于讨论union-find问题时的步骤,如下:

  • 完整而详细的定义问题,找出解决问题所必需的基本抽象操作并定义一份API;
  • 简洁地实现一种初级算法,给出一个精心组织的开发用例并使用实际数据作为输入;
  • 当实现所解决的问题的最大规模达不到期望时决定改进还是放弃;
  • 逐步改进实现,通过经验分析或(和)数学分析验证改进后的效果;
  • 用更高层次的抽象表示数据结构或算法来设计更高级的改进版本;
  • 如果可能尽量为最坏情况下的性能提供保证,但在处理普通数据时也要有良好的性能;
  • 在适当的时候将更细致的深入研究留给有经验的研究者并继续解决下一个问题。

结语

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

❓QQ:806797785

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

参考链接:

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

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

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

相关文章

基础算法-双指针,滑动窗口,位运算,区间离散化

双指针 两种类型 for(int i0,j0;i<n;i) {while(i<j&&check(i,j)) j;// 每道题目具体逻辑}双指针最核心的性质 可以优化 输入一个字符串 把每个单词输出出来 i找到单词开头 j找到空格 vector<string> rs; for(int i0,j0;i<s.size();i) {ji;while(j&…

.Net Forms Resize 12 Crack

.Net Forms Resize 12 Crack 添加了对Microsoft Visual Studio 2022(v17.5.3)及更高版本的支持。 增加了对Microsoft Windows Server 2019和2022的支持。 改进并调整引擎大小(大约快20%)。 更新与受支持的第三方控件的新版本的兼容性。 添加了新的Microsoft Framework 4.8.1库。…

in、not in、between and、基本查询数据库实验三 单表查询(一)(头歌实践教学平台)

文章目的初衷是希望学习笔记分享给更多的伙伴&#xff0c;并无盈利目的&#xff0c;尊重版权&#xff0c;如有侵犯&#xff0c;请官方工作人员联系博主谢谢。 目录 第1关&#xff1a;基本查询语句 任务描述 相关知识 查询数据表中指定字段的内容 查询数据表中的所有内容 …

Ucore lab2

练习一&#xff1a;实现first-fit 连续物理内存分配算法 根据实验指导书中的实验执行流程概述&#xff0c;先了解分析ucore如何对物理内存进行管理&#xff0c;再完成实验练习。 在对物理内存进行管理之前&#xff0c;需要先进行物理内存布局的探测&#xff0c;探测得到的内存…

[LeetCode解题报告] 1157. 子数组中占绝大多数的元素

[LeetCode解题报告] 1157. 子数组中占绝大多数的元素 一、 题目1. 题目描述2. 原题链接二、 解题报告1. 思路分析2. 复杂度分析3. 代码实现三、 本题小结四、 参考链接一、 题目 1. 题目描述 2. 原题链接 链接: 1157. 子数组中占绝大多数的元素 二、 解题报告 1. 思路分析 …

python+requests的接口自动化测试框架实例详解教程

目录 前言 一、环境准备 二、设计框架结构 三、实现框架功能 四、执行测试用例 五、总结 前言 Python是一种简单易学、功能强大的编程语言&#xff0c;广泛应用于各种软件开发和测试场景中。requests是Python中流行的HTTP库&#xff0c;支持发送HTTP请求和处理HTTP响应&a…

【c语言】多维数组原理

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; 给大家跳段街舞感谢支持&#xff01;ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ…

话说~~ HTTP协议请求的工作流程 (Web服务请求过程)最细详解

目录 文章导入 &#xff1a; 概述 &#xff1a; 详解过程 &#xff1a; DNS域名解析 &#xff1a; DNS 域名解析过程分析 &#xff1a; TCP连接搭建 &#xff1a; 等待TCP队列 建立TCP连接 发起 HTTP 请求 &#xff1a; # 哪是如何进行 HTTP 请求的呢 &#…

实验架构的部署

目录 实验要求 实验环境 1、部署静态页面 2、部署负载均衡 3、搭建动态网页 4、 nginx反向代理 5、部署NFS 7、mysql 安装mysql 安装mha 准备主从复制 开启mha 架构的部署 实验要求 完成用户访问时虚拟IP由LVS负责高可用&#xff0c;静态交给nginx处理&#xff0c;…

【工具篇】Spring Boot 整合阿里云短信-SMS

短信服务-SMS 短信服务&#xff08;Short Message Service&#xff09;是广大企业客户快速触达手机用户所优选使用的通信能力&#xff0c;分为国内短信服务和国际/港澳台短信服务。通过 API/SDK、控制台调用短信发送能力&#xff0c;将指定信息发送至国内或境外手机号码。 应…

<数据结构>NO1.算法的时空复杂度

文章目录&#x1f6a9;算法效率算法复杂度&#x1fa85;时间复杂度大O的渐进表示法常见的时间复杂度举例&#x1fa85;空间复杂度大O的渐进表示法常见的空间复杂度举例&#x1f5ef;️常见复杂度对比&#x1f5ef;️&#x1f6a9;算法效率 算法是一个被设计好的&#xff0c;计…

python 读写txt方法

​​​​​​​ 1. Python支持在程序中读写 txt文件。这里有两种方式&#xff1a; 方式一&#xff1a;使用 python内置函数&#xff0c;该函数将一个字符串的长度转换为与这个字符串长度相关的值。 例如&#xff1a;" readme"&#xff08;"r&#xff09;。 prin…

数据结构---递归转化为非递归

递归转化为非递归前言快速排序非递归归并排序的非递归前言 为什么要学习非递归写法呢&#xff1f; 当我们在用递归实现一个程序的时候&#xff0c;要考虑一个问题&#xff0c;这个程序用递归去实现&#xff0c;当数据量庞大的时候&#xff0c;会不会造成栈溢出(STACK OVERFLOW…

学习风`宇blog的websocket模块

文章目录后端代码引入依赖WebSocketConfigWebSocketServiceImpl分析tb_chat_record表WebSocketServiceImplChatConfigurator聊天消息ChatTypeEnumsWebsocketMessageDTO后端 代码 引入依赖 仅需引入以下依赖 <!-- websocket依赖 --> <dependency><groupId>…

ACM8629 立体声50W/100W单声道I2S数字输入D类音频功放IC

概述 ACM8629 一款高度集成、高效率的双通道数字输入功放。供电电压范围在4.5V-26.4V,数字接口电源支持3.3V 。在4 欧负载&#xff0c;BTL模式下输出功率可以到250W1%THDN&#xff0c;在2欧负载&#xff0c;PBTL模式下单通道可以输出1100W 1%THDN. ACM8629采用新型PWM脉宽调制架…

全国青少年软件编程(Scratch)等级考试二级考试真题2023年3月——持续更新.....

一、单选题(共25题,共50分) 1. 小猫的程序如图所示,积木块的颜色与球的颜色一致。点击绿旗执行程序后,下列说法正确的是?( ) A.小猫一直在左右移动,嘴里一直说着“抓到了”。 B.小猫会碰到球,然后停止。 C.小猫一直在左右移动,嘴里一直说着“别跑” D.小猫会碰到球,…

2023MatherCup杯三人小队手搓!(C 题 电商物流网络包裹应急调运与结构优化问题)

一个不知名大学生&#xff0c;江湖人称菜狗original author: Jacky LiEmail : 3435673055qq.com Time of completion&#xff1a;2023.4.16 Last edited: 2023.4.16 实际完成时间&#xff1a;2023/4/17 0:52 Mathematical modeling Author: HandSome Wang、BigTall Hu、Jacky L…

一个Email简单高效处理.Net开源库

推荐一个可处理电子邮件消息开源库&#xff0c;可用于消息解析、消息创建、消息修改和消息发送等功能。 项目简介 这是一个基于C#开发的&#xff0c;针对MIME&#xff08;多用途邮件扩展&#xff09;消息创建与解析&#xff0c;该项目简单易用、可用于消息解析、消息创建、消…

【Pytorch】神经网络搭建

在之前我们学习了如何用Pytorch去导入我们的数据和数据集&#xff0c;并且对数据进行预处理。接下来我们就需要学习如何利用Pytorch去构建我们的神经网络了。 目录 基本网络框架Module搭建 卷积层 从conv2d方法了解原理 从Conv2d方法了解使用 池化层 填充层 非线性层 …

Node实现 Socket 通信

socket 通信流程 Socket通信&#xff0c;首先要知道 Socket 是什么&#xff0c;就是网络上的两个程序通过一个双向的通信连接实现数据的交换&#xff0c;这个连接的一端被称为 socket &#xff0c;举一个简单的例子就是两个人在线上进行聊天&#xff0c;即线上通信&#xff0c…