算法(第四版)之并查集(union-find算法)

开个新坑, 准备学习算法(第四版), 并把上面学到的东西写成博客, 毕竟以前也学过一点算法, 但效果甚微

并查集, 在这本书的第一章1.5中叫做union-find算法, 但在其他地方这个叫做并查集,就是说一系列点的连通问题,比如, 我们有十个点, 分别记作0~9:

加入我们要把2和4连接起来怎么表示呢? 首先我们会想到,给所有的点标上一个号, 来代表他们的连通关系, 我们初始化这个数就是他们id本身:

如果我们要连接2和4, 就使得4的id为2:

之后要连接间隔点任意两个点, 就把它们和它们相应的点的值都设置为一样就行了, 如果我们要比对这两个是是否连通, 也只要比较他们的值是否相等就行了.

我们可以很轻易的写出下面的代码:

 1 /*
 2 * 并查集实现程序
 3 */
 4 public class UF {
 5     private int[] id;
 6     private int count; //连通图的个数
 7
 8     public UF(int N) { // 构造方法
 9         id = new int[N];
10         for(int i = 0; i < N; i++) {
11             id[i] = i;
12         }
13         count = N;
14     }
15
16     public void union( int p, int q) { // 连接两个点
17         int pId = find(p);
18         int qId = find(q);
19         if(pId == qId)  // 已连接则不执行任何操作
20             return;
21         for(int i : id) {
22             if(id[i] == pId)
23                 id[i] = qId;
24         }
25         count--; // 连通图数减1
26     }
27
28     public  int find(int i) { // 查找点i所在分量的标识符
29         return id[i];
30     }
31
32     public boolean connected(int p, int q) { // 查看亮点是否连通
33         return find(p) == find(q);
34     }
35
36     public int getCount() { // 获得连通图的个数
37         return count;
38     }
39
40 }

这就是树上的quick-find算法. 但是, 问题来了, 这样我们查找两个点是否连通很方便, 速度很快, 但是连接两个点就很慢了--我们需要遍历所有的点! 在例子中我们只有十个点, 可是若有几百万个点呢? 每操作一次就遍历上百万个点, 这样我们的代码肯定就会无比的慢, 所以, 我们必须想新的办法.

这是, 我们有了新的方法,就是书上的quick-union算法, 就是说, 我们每次连接两个点时, 只改变一个点的标识值, 也就形成了一棵棵树, 这样在一个连通图中就肯定有一个值的标识值是它本身的id, 没有发生改变, 当我们用查找find()方法查找时,只要一直往下找, 直到找到标识值为它本身的这个点就行, 这样就不会每次都遍历到所有的点,  总体上来说时间复杂度就降下来了:

我们也可以轻易地写出相应的代码:

 1 public void union( int p, int q) { // 连接两个点
 2             int rp = find(p);
 3             int rq = find(q);
 4             if(rq == rp)
 5                 return;
 6             if(sz[rp] < sz[rq]) {
 7                 id[rp] = rq;
 8                 sz[rq] += sz[rp];
 9             } else {
10                 id[rq] = rp;
11                 sz[rp] += sz[rq];
12             }
13             count--;
14     }
15
16     public  int find(int i) { // 查找该点的根节点
17         while(id[i] != i)
18             i = id[i];
19         return i;
20     }

但是, 还不够, 因为我们得考虑最坏的情况, 就是当我们形成的这棵数没有分支, 也就是形成了一条链时, 我们就依然变成了需要遍历所有的点, 这样, 我们辛辛苦苦降下去的佛咋读有回来了

然后, 我们就有了书上的加权的quick-union算法, 就是将树的高度记录下来, 然后每次union()时把高度低的数加到高度高的树上去, 这样树的高度就不会超过lgN, 时间复杂度也控制在了O(lgN), 代码如下:

 1 /*
 2 * 并查集实现程序
 3 */
 4 public class UF {
 5     private int[] id;
 6     private int count; //连通图的个数
 7     private int sz[];
 8
 9     public UF(int N) { // 构造方法
10         id = new int[N];
11         sz = new int[N];
12         for(int i = 0; i < N; i++) {
13             id[i] = i;
14             sz[i] = 1;
15         }
16         count = N;
17     }
18
19     public void union( int p, int q) { // 连接两个点
20             int rp = find(p);
21             int rq = find(q);
22             if(rq == rp)
23                 return;
24             if(sz[rp] < sz[rq]) {
25                 id[rp] = rq;
26                 sz[rq] += sz[rp];
27             } else {
28                 id[rq] = rp;
29                 sz[rp] += sz[rq];
30             }
31             count--;
32     }
33
34     public  int find(int i) { // 查找根节点
35         while(id[i] != i)
36             i = id[i];
37         return i;
38     }
39
40     public boolean connected(int p, int q) { // 判断两个点书否连接
41         return find(p) == find(q);
42     }
43
44     public int getCount() {
45         return count;
46     }
47
48 }

当然, 我们再有办法优化, 我们的目标当然是无限接近常数次操作, 也就是O(1), 虽然这是不可能的. 那么, 我们还能怎么优化呢? 这就是,路径压缩, 就是说在调用find()方法时同时加一个循环, 将所有的这些点直接指向根节点, 这样我们下次操作这些点不就是常数次操作了嘛! 代码如下:

 1     public  int find(int i) { // 查找根节点
 2         while(id[i] != i)
 3             i = id[i];
 4         int j = i;
 5         while(id[j] != j) {
 6             int tmp = j;
 7             j = id[j];
 8             id[tmp] = i; // 将其直接与根节点相连
 9         }
10       return i;
11     }

在最后, 附送下最后的完整代码:

 1 /*
 2 * 并查集实现程序
 3 * 使用路径压缩的加权quit-union算法
 4 */
 5 public class UF {
 6     private int[] id;
 7     private int count; //连通图的个数
 8     private int sz[];
 9
10     public UF(int N) { // 构造方法
11         id = new int[N];
12         sz = new int[N];
13         for(int i = 0; i < N; i++) {
14             id[i] = i;
15             sz[i] = 1;
16         }
17         count = N;
18     }
19
20     public void union( int p, int q) { // 连接两个点
21             int rp = find(p);
22             int rq = find(q);
23             if(rq == rp)
24                 return;
25             if(sz[rp] < sz[rq]) {
26                 id[rp] = rq;
27                 sz[rq] += sz[rp];
28             } else {
29                 id[rq] = rp;
30                 sz[rp] += sz[rq];
31             }
32             count--;
33     }
34
35     public  int find(int i) { // 查找根节点
36         while(id[i] != i)
37             i = id[i];
38         int j = i;
39         while(id[j] != j) {
40             int tmp = j;
41             j = id[j];
42             id[tmp] = i; // 将其直接与根节点相连
43         }
44         return i;
45     }
46
47     public boolean connected(int p, int q) {
48         return find(p) == find(q);
49     }
50
51     public int getCount() {
52         return count;
53     }
54
55 }

完成!

原文地址:https://www.cnblogs.com/qq1914808114/p/10765873.html

时间: 2024-11-03 05:34:25

算法(第四版)之并查集(union-find算法)的相关文章

算法复习(1)——并查集

翻翻我做过的286道题,发现忘了好多不记得啥了  呵呵呵.... 于是毅然决定老师让好好复习复习.. 第一节---并查集 1.what is 并查集?? 并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其 间要反复查找一个元素在哪个集合中.这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往 在空间上过大,计算机无法承受:即使在空间上勉

算法第四版-文字版-下载地址-Robert Sedgewick

下载地址:https://download.csdn.net/download/moshenglv/10777447 算法第四版,文字版,可复制,方便copy代码 目录: 第1章 基 础 ....................... . ..........................11.1 基础编程模型 ..................................... 41.1.1 Java程序的基本结构 ................. 41.1.2原始数据类型与表达式

算法第四版 在Eclipse中调用Algs4库

首先下载Eclipse,我选择的是Eclipse IDE for Java Developers64位版本,下载下来之后解压缩到喜欢的位置然后双击Eclipse.exe启动 然后开始新建项目,File -> New Java Project,项目名随便写,如下图 右键src文件夹,Add -> New Java Class,这里需要注意Name一栏里填写的内容就是类名,这里我写了TestAlgs4,为了测试「算法 第四版」作者给的那个测试样例 代码如下: import edu.princeto

算法(第四版)学习笔记之java实现选择排序

选择排序步骤: 1.找到数组中参与遍历比较的所有元素中的最小元素的下标: 2.将最小元素与数组中参与遍历比较的第一个元素进行交换(如果第一个元素就是最小元素的话,那么也会进行一次交换): 3.若数组中还有需要参与遍历比较的元素,则跳转到步骤1:否则排序结束. 在算法第四版中给出的所有排序均是适用于任意实现了Comparable接口的数据类型,若要将数字作为测试用例,请勿使用基本数据类型,改用Integer等实现了Comparable接口的对象. 选择排序代码如下: /** * * @author

【算法学习笔记】41.并查集 SJTU OJ 1283 Mixture

---恢复内容开始--- Description CC非常喜欢化学,并且特别喜欢把一大堆液体倒在一起. 现在CC有n种液体,其中m对会发生反应,现在她想把这n种液体按某种顺序倒入一个容器内,让她获得最刺激的体验,使危险系数尽量大. 我们可以这样计算危险系数,一开始容器内没有任何液体,危险系数为1.每次液体倒入容器时,若容器内已有一种或多种液体会与这种液体发生反应,则危险系数会乘2,否则危险系数不变. 请你求出把这n种液体倒在一起的最大危险系数. Input Format 第一行为两个数n和m.

算法第四版学习笔记之快速排序 QuickSort

软件:DrJava 参考书:算法(第四版) 章节:2.3快速排序(以下截图是算法配套视频所讲内容截图) 1:快速排序 2:

用并查集实现 kruskal算法

/**并查集实现克鲁斯卡尔算法61 2 62 4 11 4 21 3 13 5 44 5 3**/#include<iostream>#include<vector>#include<algorithm>#define MAX_N 100using namespace std; struct Node{ int numr; int numd; int val;}; int cmp(Node a,Node b){ return a.val<b.val;}vector

算法-图是否为树(并查集或深搜)

今天做了一道很有意思的一道题,这道题虽然难度只是中等,但是里面涉及到的东西却是不少.其中,我在里面学习到了并查集这个东西,虽然不是很深刻,至少有一个印象:还有深搜,一直以来,深搜和广搜都是我的弱项,本文的理解是基于别人的博客:lintcode178. graph valid tree 图是否是树.我们来看看题 题意: 给出 n 个节点,标号分别从 0 到 n - 1 并且给出一个 无向 边的列表 (给出每 条边的两个顶点), 写一个函数去判断这张`无向`图是否是一棵树 样例: 给出n = 5 并

最小生成数(并查集)Kruskal算法

并查集:使用并查集可以把每个连通分量看作一个集合,该集合包含连通分量的所有点.这两两连通而具体的连通方式无关紧要,就好比集合中的元素没有先后顺序之分,只有属于和不属于的区别.#define N 100 int father[N]; void init() { for(int i=0;i<n;i++) father[i]=1; } void union(int x,int y) //合并两元素所在集合 { x=getfather(x); y=getfather(y); if(x!=y) fathe