union-find算法
1.背景
《算法》一书中提到了关于算法的一些基本思想
- 优秀的算法因为能够解决实际的问题而变得更为重要;
- 高效算法的代码可以很简单;
- 理解某个实现的性能特点是一项有趣而令人满足的挑战;
- 在解决同一个问题的多种算法之间进行选择时,科学方法是一项重要工具;
- 迭代式改进能够让算法效率越来越高;
使用union-find算法解决连通性问题,所谓连通性问题就是在下图网络中可以看到有很多节点,节点与节点之间的连接对称为连通分量,要求编写程序判断网络中有多少组连通分量,随意给出两个节点要求判断这两个节点是否属于同一个分量。在连通图中有如下一些定义,假如一个节点p没有和任何其他节点相连,则此节点属于一个连通分量,如果一个节点p和节点q相连,则p-q为一个连通分量,如果p和q相连,q和r相连,则p-q-r为一个连通分量。
2.算法分析
……
3.算法实现
为了解决此问题,需要先设计一个API来封装所需要的基本操作:初始化,连接两个节点,判断包含某个节点的分量,判断两个节点是否属于同一个分量之中,并且返回所有分量的个数。
public class UF
UF(int N) 整数标示N个节点
void union(int p, int q) 在pq之间添加一条连线,标示连接pq
int find(int p) 节点p所在分量的标示符
Boolean connected(int p, int q) 如果p和q存在于同一个分量中则返回true
int count() 连通分量的个数
代码实现一:
import java.util.Scanner;
public class UF {
private int[] id; //分量的id
private int count; //分量的数量 //初始化分量id数组
public UF(int N) {
count = N;
id = new int[N];
for (int i = 0; i < N; i++) {
id[i] = i;
}
} //返回分量的数量
public int count() {
return count;
} //判断两个节点是否属于同一个分量
public boolean connected(int p, int q) {
return find(p) == find(q);
} //求节点p所属的分量id
public int find(int p) {
return id[p];
} //连接节点pq
//首先检查pq是否在同一个分量中,如果是则不做任何操作,否则要求p所在的连通分量中
//所有节点id必须相同,q所在的连通分量中所有节点id也相同但为另外的值,要将二者合
//二为一,则将q所在分量的所有节点id均变为p节点的id或相反
public void union(int p, int q) {
int pId = find(p);
int qId = find(q); if (pId == qId) {
return;
} for (int i = 0; i < id.length; i++) {
if (id[i] == qId) {
id[i] = pId;
}
}
count--;
} public static void main(String[] args) {
@SuppressWarnings("resource")
Scanner scanner = new Scanner(System.in);
int N = scanner.nextInt();
UF uf = new UF(N); while (scanner.hasNext()) {
int p = scanner.nextInt();
int q = scanner.nextInt();
if (uf.connected(p, q)) {
continue;
}
uf.union(p, q);
System.out.println(p + "--" + q);
}
System.out.println(uf.count() + " components");
}
}
使用这种find和union实现可以看到,对于find操作非常快,但对于union每一对节点输入都可能需要进行对整个id数组进行遍历,对于id数组的访问时间为O(N^2),所以当输入节点很多时用这个算法来统计连通分量就不行了,此时union的时间复杂度为O(N),find的复杂度为O(1)。
代码实现二:
public int find2(int p) {
while (p != id[p]) {
p = id[p];
}
return p;
} public void union2(int p, int q) {
int pId = find2(p);
int qId = find2(q); if (pId == qId) {
return;
} id[qId] = pId;
count--;
}
使用这种find2和union2实现可以看到,对于调用到find2时,在某些情况下对于id数组访问的时间复杂度依然为O(N^2),此时find2的时间复杂度为树的高度N,union2的复杂度也为树的高度N。
代码实现三:
public class WeightedQuickUF {
private int[] id; //节点的索引
private int[] w; //每个根节点对应分量的大小
private int count; public WeightedQuickUF(int N) {
count = N;
id = new int[N];
w = new int[N];
for (int i = 0; i < N; i++) {
id[i] = i;
w[i] = 1;
}
} //返回分量的数量
public int count() {
return count;
} //判断两个节点是否属于同一个分量
public boolean connected(int p, int q) {
return find(p) == find(q);
} public int find(int p) {
while (p != id[p]) {
p = id[p];
}
return p;
} public void union(int p, int q) {
int pId = find(p);
int qId = find(q); if(pId == qId) {
return;
} if (w[pId] < w[qId]) {
id[pId] = qId;
w[qId] += w[pId];
} else {
id[qId] = pId;
w[pId] += w[qId];
}
count--;
}
}
对第二种实现做一些改进,使用加权的算法,使用加权算法保证在使用union时总是将小树的根节点连接到大树上,此时find和union的复杂度均为树的高度lgN,所以访问id数组的复杂度最坏情况为cMlgN,此时C为常数,M为连接数,N为节点数。
union-find算法的更多相关文章
- 算法与数据结构基础 - 合并查找(Union Find)
Union Find算法基础 Union Find算法用于处理集合的合并和查询问题,其定义了两个用于并查集的操作: Find: 确定元素属于哪一个子集,或判断两个元素是否属于同一子集 Union: 将 ...
- LeetCode编程训练 - 合并查找(Union Find)
Union Find算法基础 Union Find算法用于处理集合的合并和查询问题,其定义了两个用于并查集的操作: Find: 确定元素属于哪一个子集,或判断两个元素是否属于同一子集 Union: 将 ...
- 最小生成树——Kruskal(克鲁斯卡尔)算法
[0]README 0.1) 本文总结于 数据结构与算法分析, 源代码均为原创, 旨在 理解 Kruskal(克鲁斯卡尔)算法 的idea 并用 源代码加以实现: 0.2)最小生成树的基础知识,参见 ...
- Kruscal算法求图的最小生成树
Kruscal算法求图的最小生成树 概述 和Prim算法求图的最小生成树一样,Kruscal算法求最小生成树也用到了贪心的思想,只不过前者是贪心地选择点,后者是贪心地选择边.而且在算法的实现中,我 ...
- MySQL 优化之 index merge(索引合并)
深入理解 index merge 是使用索引进行优化的重要基础之一.理解了 index merge 技术,我们才知道应该如何在表上建立索引. 1. 为什么会有index merge 我们的 where ...
- MYSQL-联合索引
深入理解 index merge 是使用索引进行优化的重要基础之一.理解了 index merge 技术,我们才知道应该如何在表上建立索引. 1. 为什么会有index merge 我们的 where ...
- MySQL index merge
深入理解 index merge 是使用索引进行优化的重要基础之一. [ index merge] 当where谓词中存在多个条件(或者join)涉及到多个字段,它们之间进行 AND 或者 ...
- MySQL 查询优化之 Index Merge
MySQL 查询优化之 Index Merge Index Merge Intersection 访问算法 Index Merge Union 访问算法 Index Merge Sort-Union ...
- MySQL 优化之 index_merge (索引合并)
深入理解 index merge 是使用索引进行优化的重要基础之一.理解了 index merge 技术,我们才知道应该如何在表上建立索引. 1. 为什么会有index merge 我们的 where ...
- HNCU1323:算法2-1:集合union (线性表)
http://hncu.acmclub.com/index.php?app=problem_title&id=111&problem_id=1323 题目描述 假设利用两个线性表LA和 ...
随机推荐
- ajax中的contentType使用
本文为博主原创,未经允许不得转载: 最近在修改部分项目功能的时候,遇到一个问题.局部刷新某页面的功能是由ajax实现的,但当我进行局部刷新的时候,页面并没有刷新和响应, 在后台的代码中打了断点也并没有 ...
- 【TCP/IP详解 卷一:协议】第二十二章 TCP的坚持定时器
这两章来到了TCP的定时器部分,在 TCP的超时与重传 和 TCP的三握四挥 我们介绍了 TCP的重传定时器 和 TCP的2MSL定时器. 本随笔介绍 防止返回ACK丢失的死锁情况 的 坚持定时器 和 ...
- 【TCP/IP详解 卷一:协议】第9章 IP选路
推荐链接:网络地址与主机地址 9.1 引言 路由选择程序(daemon),通常这是一个用户进程.在大多数的Unix系统中,大多数的路由选择程序都是路由程序和网关程序. 路由表经常被IP访问,但是它被路 ...
- python 获取本地语言和编码的代码
#! /usr/bin/env python # encoding=utf8 import locale language, encoding = locale.getdefaultlocale() ...
- django多对多中间表详解
我们都知道对于ManyToMany字段,Django采用的是第三张中间表的方式.通过这第三张表,来关联ManyToMany的双方.下面我们根据一个具体的例子,详细解说中间表的使用. 一.默认中间表 首 ...
- 30分钟带你了解Docker
最近一直在忙项目,不知不觉2个多月没有更新博客了.正好自学了几天docker就干脆总结一下,也顺带增加一篇<30分钟入门系列>.网上能够查到的对于docker的定义我就不再重复了,说说我自 ...
- 测试工作中经常用到的一丢Linux命令
自己平时测试工作中经常要在Linux下搭建测试环境,有涉及到启动/终止服务器,修改tomcat配置文件,偶尔碰到端口被占用... 这时就不得不需要一些基本的Linux命令来处理遇到的这些问题 1.cd ...
- English trip -- FC(万词辩音王)
五个元音字母+一个半元音字母 1.发字母本身音都是 元+辅+e 2.不发字母音基本都是辅音结尾或者两个相同字母 辅+元+辅;元+辅 开音节 ...
- Confluence 6 更新目录
当编辑目录时候的限制 你不能对你用户属于的目录进行编辑,禁用或者删除.这个能够预防管理员通过修改目录的时候讲自己属于的管理员权限从系统管理员组中删除. 这个限制对所有的用户目录类型适用.例如: 如果当 ...
- iOS UI-UIPickerView(拾取器)、UIWebView(网页视图)和传值方式
// // ViewController.m // IOS_0107_finalToolClass // // Created by ma c on 16/1/7. // Copyright (c) ...