您的位置:首页 > 其它

并查集

2017-01-12 15:08 218 查看

介绍

Algorithm Part1的总结第一篇,按照课程的顺序,加入自己对数据结构理解和对其应用作业的尝试过程。

第一部分是union-find,中文也叫做并查集,维基解释是它是一种不相交的数据结构,但想要理解这个数据结构,这个解释还是太抽象了。

先介绍API:

void union(int p, int q)


将p与q相连

boolean connected(int p, int q)


判断p与q是否相连

从这两个方法就可以想到这是一个用于判断两点是否相连的数据结构,与树很相似但更注重相连的状态来进行数学计算。下来附图解释两个方法:



quick find

这是该数据结构第一种实现方法,非常的直观。

定义

用数组id[]存储数字ID对应的值

当且只当id[p]=id[q],q与p相连

如图:



接着是上述方法的实现:

connected:将id[p]与id[q]做比较,若相等则相连。

union:将所有值为id[p]的项值改成id[q]。

实现代码:

public class QuickFindUF
{
private int[] id;
public QuickFindUF(int N)
{
id = new int
;
for (int i = 0; i < N; i++)
id[i] = i;
}
public boolean connected(int p, int q)
{ return id[p] == id[q]; }
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;
}
}


需要注意是在初始化时顺序是id从小到大,所以相连的项的值都是id最小的值。

connected与定义一致,而union默认将id[q]赋给id[p]。

分析

algorithminitializeunionfind
quick-findNN1
查找(find)很快是O(1)但连接(union)很慢是O(n)。

quick union

那么就需要改进,推出版本二。

定义

用数组id[]存储数字ID对应的值

id[i]存储的是i的父节点的ID

root(相当于quick find靠前的点)的ID为id[id[id[…id[i]…]]].

如图:(于上图内容相同)



于是需要多一个方法去寻找根

int root(int i)


实现改变为:

find:比较p与q的root是否相同

union:将p的root的值赋为p的root的值

实现代码:

public class QuickUnionUF
{
private int[] id;
public QuickUnionUF(int N)
{
id = new int
;
for (int i = 0; i < N; i++) id[i] = i;
}
private int root(int i)
{
while (i != id[i]) i = id[i];
return i;
}
public boolean connected(int p, int q)
{
return root(p) == root(q);
}
public void union(int p, int q)
{
int i = root(p);
int j = root(q);
id[i] = j;
}
}


分析:

algorithminitializeunionfind
quick-findNN1
quick-unionNNN
union的消耗包括了find

quick find 的union方法太耗时,在使树扁平上消耗太大。

qiuck union的find方法太耗时,并且树很高,导致union也很慢。

版本二只是初步改善,并未体现太大优势。

weighted quick-union

版本三在版本二基础上,通过改善使树变得扁平。

定义:

记录每棵树的容量即节点数

将容量小的树的root连接到容量大的树的root,容量的大的树的root成为了容量小的树新的root

如图:



于是我们需要额外维护一个数组sz[i]来记录每个节点(也就一开始ID)的容量,如果未与其他节点相连,容量为1,否则即是它的子节点的个数。

实现改为:

union:从原来的默认p的root改为q的root变成smaller树的root改为bigger树的root,同时改变sz[]的值(容量发生变化)

实现代码:

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]; }
}


分析:

algorithminitializeunionfind
quick-findNN1
4000
quick-union
NNN
weighted quick-unionNlgNlgN
经过平衡,树的高度被控制在lgN,因此对应方法时间复杂度也为O(lgN),有了显著提高。

对于树的高度还可以进一步的改进,如图:



但实际上不可能每加一个点就对所有节点进行这样的调整即将部分节点直接指向root,需要在使用很频繁的int root(int i)中进行调整。

实现代码:

private int root(int i)
{
while (i != id[i])
{
id[i] = id[id[i]];
i = id[i];
}
return i;
}


实现代码中的实现与图例有差距,简单来说就是减少了树一半的高度,基本达到树的扁平化要求。同时此代码改变了原来的父节点,也就是说父节点在这个数据结构中并不重要,重要的是root节点,也就是节点之间相连与否的状态。

应用作业

题目见

http://coursera.cs.princeton.edu/algs4/assignments/percolation.html

简单来说就是通过模型模拟现实的材料,用上统计学的方式探索材料中的空隙占总材料体积的p%可以基本保证可以被渗透。

首先要明白几个要点:

有两种节点状态open和closed,对于一组点(包括一个点)有三个状态connected but not full,connected and full,closed,connected的点必定open

full的意思与顶上的某个点相连(和水渗透油相似),最终目标percolation是上下相连

原图:



API:

void open(int row, int col)
boolean isOpen(int row, int col)
boolean isFull(int row, int col)
boolean percolates()


实现方法:

isOpen是判断点的方法,由于借用WQU的数据结构,对于点的状态必须自己解决,用一个boolean一维数组openblocks[]存储各个点的状态,用

int row = (num - 1) / n + 1;

int col = (num - 1) % n + 1;(n为边长)

num = (row - 1) * n + col;

相互转换。

open则稍显复杂,首先将这个节点对应的boolean值改成true,再与周围的节点boolean为true的节点union。

isFull和percolates最为棘手,需要先用两个虚拟节点分别连接首行和尾行,才能满足时间复杂度的要求常数次connected的时间,但这会导致这其中最困难的问题backwash,由于两个虚拟节点导致顶部和底部的隐性连通,进而导致本来isFull()为false的节点变为true,所以必须维护两个WQU来分别应对两个方法。

而这道题用了正确的方法,时间和内存是很充裕的。

实现代码:

public class Percolation {
private int visualTop;
private int visualButtom;
private int n;
private WeightedQuickUnionUF wVirtualUF;
private WeightedQuickUnionUF wAuatualUF;
private boolean[] blocks;

public Percolation(int n) {
// create n-by-n grid, with all sites blocked
if (n <= 0)
throw new IllegalArgumentException();
else
this.n = n;

visualTop = 0;
visualButtom = n * n + 1;
wVirtualUF = new WeightedQuickUnionUF(n * n + 2);
wAuatualUF = new WeightedQuickUnionUF(n * n + 1);
blocks = new boolean[n * n + 2];
blocks[visualTop] = true;
blocks[visualButtom] = true;
for (int i = 1; i <= n; i++) {
wAuatualUF.union(visualTop, i);

wVirtualUF.union(visualTop, i);
wVirtualUF.union(visualButtom, visualButtom - i);
}

}

public void open(int row, int col) {
// open site (row, col) if it is not open already
if (checkBound(row, col)) {
blocks[(row - 1) * n + col] = true;
if (checkBound(row - 1, col)) {// open bottom site
if (blocks[(row - 2) * n + col]) {
wVirtualUF.union((row - 1) * n + col, (row - 2) * n + col);
wAuatualUF.union((row - 1) * n + col, (row - 2) * n + col);
}
}
if (checkBound(row + 1, col)) {// open top site
if (blocks[row * n + col]) {
wVirtualUF.union((row - 1) * n + col, row * n + col);
wAuatualUF.union((row - 1) * n + col, row * n + col);
}
}
if (checkBound(row, col - 1)) {// open left site
if (blocks[(row - 1) * n + col - 1]) {
wVirtualUF.union((row - 1) * n + col, (row - 1) * n + col - 1);
wAuatualUF.union((row - 1) * n + col, (row - 1) * n + col - 1);
}
}
if (checkBound(row, col + 1)) {// open right site
if (blocks[(row - 1) * n + col + 1]) {
wVirtualUF.union((row - 1) * n + col, (row - 1) * n + col + 1);
wAuatualUF.union((row - 1) * n + col, (row - 1) * n + col + 1);
}
}
} else
throw new IndexOutOfBoundsException();
}

public boolean isOpen(int row, int col) {
// is site (row, col) open?
if (checkBound(row, col)) {
return blocks[(row - 1) * n + col];
} else
throw new IndexOutOfBoundsException();

}

public boolean isFull(int row, int col) {
// is site (row, col) full?
if (checkBound(row, col)) {
return isOpen(row, col) && (wAuatualUF.connected((row - 1) * n + col, visualTop));
} else
throw new IndexOutOfBoundsException();
}

public boolean percolates() {
// does the system percolate?
for (int i = 1; i <= n; i++) {
int row = (i - 1) / n + 1;
int col = (i - 1) % n + 1;
if (isOpen(row, col)) {
return wVirtualUF.connected(visualTop, visualButtom);
}
}
return false;

}

public int numberOfOpenSites() {
// number of open sites
int count = 0;
for (int i = 1; i <= n * n; i++) {
if (blocks[i])
count++;
}
return count;
}


代码之中比较棘手的是percolate的边界情况,在只有一共一个节点的时候,由于虚拟节点已经上下连通,又因为percolate无从判断boolean[],所以只能遍历顶部的节点比较boolean值是否有为true的节点。

continuing…
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: