您的位置:首页 > 其它

跟着Sedgewick学算法(week 1 UnionFind)

2014-06-21 22:27 225 查看


发现笔记转过来,没有图的~~~~~~~~~~~悲剧,给出共享笔记链接

https://www.evernote.com/pub/yanbinliu/algorithm

很久之前就在coursera看到Robert的算法课程,当时太懒惰,没有跟着学习。前两天逛coursera偶然发现又开课了,于是毫不犹豫跟着

走起。

基本上每一个课程开始,总会有introduction blabla之类的。Robert的算法课程,上来不到10分钟的欢迎致辞,然后就直接从一个例子讲起,来分析一个问题。

建模,找到算法求解,找不足,改进,迭代直到满意为止。

首先给出的问题是dynamic connectivity,动态连通问题,就是给出譬如

(1,2)

(7,3)

(4,9)



……

等连接在一起的点对,

然后判断,一对(p,q)之间是否存在连通路径。

为了解决这个问题,主要分为两步:

1.将给出的点对连接起来,即Union的过程

2.判断一个对点(p,q)之间是否有连通路径

一个比较直观的应用就是迷宫…………,不过当然不止如此,譬如数字图像,网路,电路等等。

为了简化问题,忽略其他无关因素,只考虑0-9这10个数字。

首先,给出的分析quick find

顾名思义,可知这种算法,可以快速判断两个点是否连接。

采用长度为10的数组,数组索引标识点自身,数组值表示与其数组索引连接的点,



如图所示,0,5,6是连通的,1,2,7也是连通的。
某大牛说,算法+数据结构=程序,其实很大程度上有些问题,一旦给出了数据结构,算法就水到渠成了的,虽然这里是个很小的问题。
下面就是简单的c#代码

public class QuickFind
[align=left] {[/align]
[align=left] public List< int> list;[/align]
[align=left] public QuickFind( int n)[/align]
[align=left] {[/align]
[align=left] list = new List< int>(n);[/align]
[align=left] for ( int i = 0; i < n; i++)[/align]
[align=left] {[/align]
[align=left] list.Add(i);[/align]
[align=left] }[/align]
[align=left] }[/align]
[align=left] public void AddUnion( int p, int q)[/align]
[align=left] {[/align]
[align=left] var pid = list[p];[/align]
[align=left] var qid = list[q];[/align]
[align=left] for ( int i = 0; i < list.Count(); i++)[/align]
[align=left] {[/align]
[align=left] if (list[i] == pid) list[i] = qid;[/align]
[align=left] }[/align]
[align=left] }[/align]
[align=left] public bool Connected( int p, int q)[/align]
[align=left] {[/align]
[align=left] return list[p] == list[q];[/align]
[align=left] }[/align]
[align=left] public void writeResult()[/align]
[align=left] {[/align]
[align=left] foreach ( var item in list)[/align]
[align=left] {[/align]
[align=left] Console.Write(item.ToString()+ ' ');[/align]
[align=left] }[/align]
[align=left] }[/align]
[align=left] [/align]
[align=left] }[/align]

提出了算法,就要简单分析一下复杂度之类的,blabla的,如下图



对于find来讲,复杂度很低,只要O(1)就行,每次只是需要比较两个索引点的值就行,
但是对于union的动作就有点高了,对于N个点对来实现union命令,复杂度是O(N^2)(N的平方)。



接下来分析quick union 算法
同时是以数组作为数据结构,只不过每个索引点中的值是该点的父节点,知道索引值等于存储值为止,最终的节点就是root节点,上图比较容易懂



3的root是9,root节点相同的,就是连通的。



自然而然就可以得到简单的代码实现

[align=left]public class QuickUnion[/align]
[align=left] {[/align]
[align=left] int[] list;[/align]
[align=left] public QuickUnion( int n)[/align]
[align=left] {[/align]
list = new int
;
[align=left] for ( int i = 0; i < n; i++)[/align]
[align=left] { [/align]
[align=left] list[i]=i;[/align]
[align=left] }[/align]
[align=left] }[/align]
[align=left] public bool Find( int p, int q)[/align]
[align=left] {[/align]
[align=left] return root(p) == root(q);[/align]
[align=left] }[/align]
[align=left] public void AddUnion( int p, int q)[/align]
[align=left] {[/align]
[align=left] int i = root(p);[/align]
[align=left] int j = root(q);[/align]
[align=left] if (i == j) return;[/align]
[align=left] list[i]=j;[/align]
[align=left] }[/align]
[align=left] private int root( int p)[/align]
[align=left] {[/align]
[align=left] while (p != list[p])[/align]
[align=left] { [/align]
[align=left] p = list[p];[/align]
[align=left] }[/align]
[align=left] return p;[/align]
[align=left] }[/align]
[align=left] public void writeResult()[/align]
[align=left] {[/align]
[align=left] for ( int i = 0; i < list.Count(); i++)[/align]
[align=left] {[/align]
[align=left] Console.Write(list[i].ToString()+ ' ');[/align]
[align=left] }[/align]
[align=left] }[/align]
[align=left] }[/align]



算法复杂都分析如上图。
quick-find的缺点是union动作代价太高,虽然保持了形成的树是flat的,但是为了保持flat代价太高
quick-union的缺点是形成的树太高,而且find操作代价高

当然了,有了解决方案,但是效果不理想,下一步想的就是怎么improvement了~~

Improvement 1

改进一:利用weighted quick-union,即在每次union的时候,将size比较小的树的根指向size较大的树的根,
而不是反过来。这里size大小指的是每个树的节点的多少,而不是树的高度(当然也可以是,只不过节点数比较好计算)。



这当然是一个挺好的改进,不过让我想到空间和时间的不可完美统一。虽然时间上改进了,但是空间上需要多出一个size数组来保持树的大小,所以凡事没有完美的~~

下图是一个100个sites,88个union操作得出的结果,weiggted算法效果还是很显著的



代码如下

[align=left]public class WeightedQuickUnion[/align]
[align=left] {[/align]
[align=left] int[] list;[/align]
[align=left] int[] size;[/align]
[align=left] public WeightedQuickUnion( int n)[/align]
[align=left] {[/align]
list = new int
;
size = new int
;
[align=left] for ( int i = 0; i < n; i++)[/align]
[align=left] {[/align]
[align=left] [/align]
[align=left] list[i]=i;[/align]
[align=left] size[i]=1;[/align]
[align=left] }[/align]
[align=left] }[/align]
[align=left] public bool Find( int p, int q)[/align]
[align=left] {[/align]
[align=left] return root(p) == root(q);[/align]
[align=left] }[/align]
[align=left] public void AddUnion( int p, int q)[/align]
[align=left] {[/align]
[align=left] int i = root(p);[/align]
[align=left] int j = root(q);[/align]
[align=left] if (i == j) return;[/align]
[align=left] if(size[i] < size[j])[/align]
[align=left] {[/align]
[align=left] list[i] = j;[/align]
[align=left] size[j] += size[i];[/align]
[align=left] }[/align]
[align=left] else[/align]
[align=left] {[/align]
[align=left] list[j] = list[i];[/align]
[align=left] size[i] += size[j];[/align]
[align=left] }[/align]
[align=left] }[/align]
[align=left] private int root( int p)[/align]
[align=left] {[/align]
[align=left] while (p != list[p])[/align]
[align=left] {[/align]
[align=left] p = list[p];[/align]
[align=left] }[/align]
[align=left] return p;[/align]
[align=left] }[/align]
[align=left] public void writeResult()[/align]
[align=left] {[/align]
[align=left] for ( int i = 0; i < list.Count(); i++)[/align]
[align=left] {[/align]
[align=left] Console.Write(list[i].ToString()+ ' ');[/align]
[align=left] }[/align]
[align=left] }[/align]
[align=left] }[/align]
简单分析下:
find操作需要判断节点的root是否一致,所以和节点的深度相关
union操作在给定节点root的情况下,是话费常量时间的。
然而,具有N个节点的weighted quick-union树的深度是不大于lgN的(2为底的)。

pf:





复杂度分析,就此我们得到满意答案了吗?

Of Course NOT!!

Improvement 2:path compression

即quick-union with path compression,
对一个一个节点p,直接将其父节点设为root节点,那就是极好的了。











这几幅图,可以很好的展现出算法的实现原理。但是,但是,但是,这只需要添加一句代码就ok的啊,竟然只要一句的
[align=left] private int root(int p)[/align]
[align=left] {[/align]
[align=left] while (p != list[p])[/align]
[align=left] {[/align]
[align=left] list[p] = list[list[p]];//添加这一句[/align]
[align=left] p = list[p];[/align]
[align=left] }[/align]
[align=left] return p;[/align]
[align=left] }[/align]
一句话:No reason not to!

不可否认啊,效果好的算法一般分析起来,就没那么看起来简单了~~amortized analysis

[Hopcroft-Ulman,Tarjan] Starting from an empty data structure, any sequence of M
union-find ops on N objects makes ≤ c ( N+ Mlg* N)array accesses

简单的算法,优雅的数学~~

但是,又是但是,每次都会有但是
虽然在实践中,WQUPC是线性的,但是理论确不是,甚至理论上不存在理论的算法。

复杂度分析大阅兵



10^9个union和find对10^9节点,WQUPC可以将时间从30年降到6s~~这就是算法的强大
~~

最后的最后,一个实践。
渗透概率问题,



对于一个节点是否open的概率p,与是否渗透的概率之间有什么关系~~
如图



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