week1——并查集

声明

本文是博主在Coursera学习时所写的学习笔记,如有错误疏漏还望各位指正。

如果大家转载,请注明本文地址!

基础知识

什么是Dynamic Connectivity
Given a set of N objects.

  • Union command: connect two objects.

  • Find/connected query: is there a path connecting the two objects?

例如给定集合如下:
述
此时

  • coonconnected(0, 7) False

  • connected(8, 9) True

执行以下命令:

  • union(5, 0)
  • union(7, 2)
  • union(1, 0)
  • union(6, 1)

集合的状态改变为:
这里写图片描述
此时:

  • connected(0, 7) Ture

我们设定“连接”有以下属性:

  • 自反性:p和p是互相连接的
  • 对称性:如果p与q是的连接,则q与p是连接的
  • 传递性:如果p与q连接,q与r连接,则p与r连接

再给出一个定义

  • 连接组件:相互连接的对象的最大集合。

给定如下集合:
这里写图片描述
上图中包含三个连接组件
{0},{1,4,5},{2,3,6,7}

在同一个连接组件中的所有节点都是互相连接的

———————分割线——————
说明:本文介绍的算法采用Java来实现

Quick Find

根据上面的问题描述,我们所定义的类需要包含以下三个函数:

  • UF(int N) 初始化,创建N个节点
  • void union(int p,int q) 链接p和q
  • boolean connected(int q,int q);p和q是否在同一个链接组件中

采用的数据结构
- 长度为N的数组id[],
说明:

  • id[i]的值表示节点i所在的连接组件的id
  • p和q相互连接的条件:当且仅当id[p] == id[q]

实现代码如下(Java)

public class QuickFindUF
{
    private int[] id;
    //构造函数 访问N次数组
    public QuickFindUF(int N){
        id = new int[N];
        for (int i = 0; i < N; i++)
            id[i] = i;
    }
    //判断p、q是否连接,访问两次数组
    public boolean connected(int p, int q){ 
        return id[p] == id[q];
    }
    /*
    *把p,q所在的两个连接组件连接起来
    *最多访问2N+2次数组
    */
    public void union(int p, int q)
    {
        int pid = id[p];
        int qid = id[q];
        for (int i = 0; i < id.length; i++)
            if (id[i] == pid) 
                id[i] = qid;
    }
}

Quick Union

数据结构:
- 长度为N的数组 id[]

说明:

  • id[i] 表示节点 i 的父节点
  • root(i)表示包含i的树的根节点
    例如
    这里写图片描述
    root(3) 的值为9

不同于方式一的地方

  • find. 判断p和q的根节点是否相同
  • union. 让p的根节点指向q的根节点
    这里写图片描述
    union(3,5)
    这里写图片描述

实现代码如下(Java)

public class QuickUnionUF
{
    private int[] id;
    public QuickUnionUF(int N)
    {
        id = new int[N];
        /*
        *让每个节点指向它自己
        *访问N次数组
        */
        for (int i = 0; i < N; i++)
            id[i] = i;
    }
    /*
    *寻找当前节点的根节点
    *访问数组的次数取决于树的高度
    */
    private int root(int i)
    {
        while (i != id[i]) i = id[i];
            return i;
    }
    /*
    *检测p,q是否具有相同的根节点
    *访问数组的次数取决于包含p、q的树的深度
    */
    public boolean connected(int p, int q)
    {
        return root(p) == root(q);
    }
    /*
    *检连接p、q(讲p的根节点指向q的根节点)
    *访问数组的次数取决于包含p、q的树的深度
    */
    public void union(int p, int q)
    {
        int i = root(p);
        int j = root(q);
        id[i] = j;
    }
}

改进

对quick union 加权

quick union 得到的森林可能树会过高,导致效率很低
这时就可以采用Weighted quick-union.
- 保存每棵树的大小(节点数,而非高度)
- 每次union让小的树指向大的树
下面是quick-union 与Weighted quick-union的区别
这里写图片描述

对一百个节点进行88次union操作,结果如下
这里写图片描述
显然,采用Weighted quick-union会使得到的树更加扁平化,加快算法效率
说明:

  • 最终结果深度小于lgN
  • 父节点的大小大于任一子节点的大小*2;
    这里写图片描述
    x的深度小于lgx
    |T2 | > 2|T1 |
    算法实现(Java)
    修改quick-union的两个函数
int sz[];//增加一个数组记录大小
public QuickUnionUF(int N)
    {
        id = new int[N];
        sz = new int[N];
        /*
        *让每个节点指向它自己
        *访问N次数组
        */
        for (int i = 0; i < N; i++)
        {
            id[i] = i;
            sz[i] = 1;    //初始化大小为1
        }
    }


    public void union(int p, int q)
    {
        int i = root(p);
        int j = root(q);
        if (i == j) 
            return;
        //判断两棵树的大小
        if (sz[i] < sz[j]){
            id[i] = j;
            sz[j] += sz[i]; 
        }
        else{ 
            id[j] = i; 
            sz[i] += sz[j]; 
        }
    }




路径压缩

每次计算p的根节点后,直接让p指向根节点
这里写图片描述
对p节点进行路径压缩
这里写图片描述

路径压缩可以显著的提高结果扁平化的程度,从而加快算法效率

代码实现(Java)
修改int root(int i)

private int root(int i)
{
    while (i != id[i])
    {
        id[i] = id[id[i]];   //只需要增加这一行代码
        i = id[i];
    }
    return i;
}

比较

对包含N个节点的集合进行M次union-find操作
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值