算法导论之并查积

  今天有空把算法导论的并查积那一章看了一下,最后一节因为牵涉到平摊分析那一块儿所以没法看。在此之前没有学过并查积,不过感觉挺难得。其实想想也明白就该有这么东西,因为很多地方都会用到这种算法,虽然不知道是什么,但是肯定有。看并查积是因为前天在poj做了个题,感觉暴力的思路可以得出正确答案,但是数据规模极大,怀着侥幸的心理sub后直接返回了一个TLE,比较悲剧,也不明白会跟并查积扯上什么关系,上discuss里面一看,原来得用并查积。那也不也不会。刚好dp看了几天都烦了,今天就看了会儿并查积。

  算法导论上是21章-用于不相交集合的数据结构,国外的书上都是这么说的。算法导论上面讲的比较详细。根据我能记住的就是对一个大的集合,初始的时候就是每个元素在一个独立的集合中。每个集合有个代表(沿用算法导论上的名字),同意集合中的每个元素的代表就是集合的代表。初始的时候就是元素自己。然后根据元素之间的关系,合并集合。元素之间的关系即判断他们的代表是否相同,相同就是一个集合,如果不相同但是属于一个集合,就要把他们合并。实现并查积的数据结构可以是链表和数组。根据书上讲得貌似时间耗费也不是相差很大,不过觉得用数据比较简单。如果用链表的话每个集合应该含有一个头元素和一个尾元素。头元素指向代表即第一个元素,尾元素指向最后一个元素。每个成员应该保存代表指针这个属性。用数组实现的话就是用树来形容集合。每个元素i设置一个数组p[i]是他的父亲。初始化p[i]=i。

  一般的集合根据输入分为动态和静态的。静态的就是集合是固定的,可以在集合上进行运算。可以用深度优先或者广度优先搜索来求不相交集合。时间上应该比并查积要快。所谓动态的集合就是集合需要动态的维护,也就是说不知道集合到底有哪些元素,要根据输入随时调整集合的状态。所以使用并查积能够有效解决此类问题。下面主要讲两点,关于两个元素是否属于一个集合的判定和集合的合并该如何优化的问题。时间复杂度就不做讨论了。

  这两个子问题在时间上应该是此消彼长,这虽然是个比较模糊的概念,但是很明显,如果find()花的时间长union就简单了。关于这两个问题大师们的理论已经足够解决acm中遇到的问题了。不相交集合的链表示能够改进的方法有一种加权合并启发式策略,按我的理解就是把少的加到多的上去,减少时间。道理很简单,把少的加到多的上去总比多的加到少的上去快吧。如果是有根树的话,即用森林表示不相交集合,感觉要简单的多,因为树可以用数组表示。时间上也可以得到改进。有两种启发式策略:(1)按秩合并。(2)路径压缩。第一种就跟链表里面讲的差不多,秩表示节点高度的上限。在合并的时候把秩较小的根指向较大的根。听起来比较简单。路径压缩是一种非常简单有效地方法。因为find中,把查找路径上所有节点都指向了根节点。说白了就是find递归运行了一遍直到遇到了根,再返回,更新沿途的每个节点,使其指向根。这个过程中秩是没有变化的。然后就是union,union就是根据秩修改父节点罢了,因为现在父节点就意味着元素所在集合的代表,也就是根。union(x, y)的步骤就是把秩小的树根指向秩大的树根。如果相等的话,随便挑一个就行,然后把秩加上一,因为树高变了。有的书上并不是这样讲的,比如数据结构与算法分析-c语言描述上就是用了比的说法,但是大同小异吧。算法设计上与算法导论使用的方法是一样的。

  经过此番处理,基本上就能把同属于一伙的元素标记出来了,但是一般最后要用一个对每个元素用一次find操作来找到它的代表,因为合并过程中并没有改变全部元素的代表指向,这个地方尤其需要注意。LRJ的书上没有做深入的说明,题目倒是巨难无比。如需更多了解可参照算法导论。

posted @ 2011-02-14 20:07  like@neu  阅读(307)  评论(0编辑  收藏  举报