当前位置: 代码迷 >> 综合 >> week1——并查集
  详细解决方案

week1——并查集

热度:8   发布时间:2023-12-22 11:00:27.0

声明

本文是博主在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操作
这里写图片描述