ImageVerifierCode 换一换
格式:DOCX , 页数:17 ,大小:142.25KB ,
资源ID:16755706      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/16755706.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(算法合集之《平面嵌入》Word文档下载推荐.docx)为本站会员(b****3)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

算法合集之《平面嵌入》Word文档下载推荐.docx

1、 12总结-12二附录-12 1MergeBiconnectedComponent伪码 -13 2walkup伪码-13 3walkdown伪码-14算法1.平面嵌入的相关定义如果对平面嵌入还有些陌生,希望下面的定义对你有所帮助。(1)平面作图:一张图能够转化会为一张所有边都不相交的图(在节点上相交不算),转化过程就叫做平面作图。(原来相连的节点在转化后依然相连,图1展示了平面作图)(2)平面图:一张图能够进行平面作图它就是平面图,否则为非平面图。(3)平面嵌入:和平面作图是等价的,不过在储存方式上是这样的,对于每个节点都顺时针储存和它连接节点。我们将记录用的表叫邻接表。注意:本篇论文所有的平

2、面都是指这里的相关定义,和几何平面是不同的。 图12算法的目的 在明白了平面嵌入的一些基本定义以后。对于给定的图G,它有n个节点,m条边。(以后我们将始终用n表示图G的节点个数,m表示图G的节点边数)算法的目的就是用O(n)的时间判断一个图是否为平面图,如果是的话要用O(n)的时间实现平面嵌入。3一些相关的知识 为了实现我们的目的,我们必须先知道一些必要的知识。这些知识将包含深度优先搜索,双连通分量,关节点,计数排序(一个复杂度和关键码范围有关的算法)。还要知道一些必要的定理,如一个平面图的边将不能超过3n-5条边,否则它将是一个非平面图。Kuratowiski曾经证明了两个图将阻碍平面嵌入的

3、进行,那就是图2所描述的图,我们称作k5图和k33图。一张图中如果包含了k5图和k33图的同胚图(如果一个图可以通过延展,弯曲,合并连接节点的方式转化为另一张图,这两张图就是同胚的),那么这张图一定是非平面图,反之也成立。 我们的算法有一个基本操作,就是加边操作。我们创建一个图GP,我们要将原图中的边按照一定的顺序加入图GP中。我们把这个操作称作边的嵌入。 为了叙述的通顺性,有关复杂度的分析和正确性的证明我都会放到论文的末尾。 图24算法总览为了后面的叙述更加清晰,这里将叙述算法的总体过程。我们只讨论对双连通分量图的平面嵌入,对于有关节点的图,我们可以将图从关节点处断开分别进行平面嵌入后再合并

4、。首先对图进行一次深度优先遍历,得到一棵深度优先搜索树和图的逆向深度优先搜索序(由于深度优先搜索序简称DFI序,所以后面写作逆向DFI序)。我们将每个节点和它儿子相连的边叫做树边,将和它后裔相连的边叫做回落边(注意:我们指的后裔是不包括儿子的)。然后我们按照逆向的DFI序依次处理每个节点。所谓处理节点就是:首先将它的树边嵌入图GP中,然后将它的的回落边都嵌入到图GP中,我们并不处理它和它祖先相连的边(祖先是包括父亲的)。如果嵌入过程失败,那么可以得到图G是非平面图的结论,否则就完成构造。5边的嵌入我们算法最重要和操作就是对边的嵌入,我将针对图3进行详细的说明,在图3(a)中,我们要嵌入边(v,

5、w)。在开始的时候该连通分量包含了一个关节点r,我们将r去掉后该连通分量就变成了两个部分,就象图3(b)。然后分别给两个分量配上一个r, 这样r就被分成了两个,象图3(c)。然后将边(v,w)嵌入,然后将两个连通分量合并在一起,这时候,r已经不再是关节点了,象图3(d)。这样我们就完成了一个最基本的嵌入操作。为什么要这么麻烦呢?大家注意到图3(d)了吗?它的下半部分左右颠倒了,这也是嵌入时候的一个操作,叫做反转操作,这个操作是为了保证图GP的一个重要性质,这些都将在下文中提到。 图3在我们的嵌入过程中,只要图GP中含有关节点,我们都将从这个关节点把它所在的连通分量断为两个部分。在嵌入回落边的时

6、候又会将他们组合在一起。这样,图GP就是由很多的双连通分量组成的,请大家注意,在代码的实现中我们必须实际的这样处理,并不是为了理解。6.外部活跃,相关与内部活跃反转操作所要保护的性质就是:在嵌入边的过程中所有的外部活跃节点都必须留在外部面上。什么是外部面呢?外部面的名称很形象,也就是图中接触到最外层空间的点和边构成的面,并且外部面都会是完整的环,如果一个双连通分量只有一条边,那么我们就要进行相应的转化,像图4这样。 图4外部活跃节点的定义就要复杂些,为了清楚的叙述,我们要加入些新的定义。我们将双连通分量中DFI序最小的节点称作该双连通分量的根节点。由于我们在只有嵌入树边的时候才会产生分离操作,

7、所以我们会发现当一个关节点被分离成多个后,和儿子分到一个分量中的节点一定会成为该分量的根节点。我们把这些节点称作复制点,把唯一的不是根的点称作原节点。假设r是一个双连通分量B的根节点,那么r 在该双连通分量中一定只有一个儿子,若r有两个儿子c1,c2,由于是深度优先遍历,在访问到c1后,继续访问的过程中将通过双连通分量B的某一条路径不经过r 到达c2,那么c2将不会成为r的儿子,也就与假设矛盾。我们设定r 的这个唯一儿子为c,那么我们将r 到c 的边称做根边(由于我们只有在加入树边的时候才会导致双连通分量的拆分,所以根边一定是树边),将该根节点称作节点(儿子还没有确定的我们写作r),将该双连通

8、分量称做块。这样就可以和原r 节点区分开并且双连通分量也可以明确表示(后来的叙述都将遵从这里的设定)。 子块的定义在这里就产生了,我们将块称做原节点r 的子块。知道了这些后,只要满足下面两个条件的任何一个,它就是外部活跃节点:(1) 当我们在处理节点v的时候,该节点处理过,并且有直接连接v的祖先的边。(2) 当我们在处理节点v的时候,该节点处理过,并且该节点的子块中存在外部活跃节点。就象图5中,方形的点都是外部活跃节点。那么我们怎么得到外部活跃节点的信息呢?我们把每个节点本身能够直接到达的最早祖先的深度记做该节点的ecpoint,把能够直接或者间接到达的最早祖先的深度叫做该接点的lowpoin

9、t(所谓间接就是通过它的子孙到达)。同时,我们给每个节点配备一个 图5图5:z有直接连接v的祖先u的边所以,v是外部活跃节点,,z在d的子块中,所以d也是外部活跃节点,d在x的子块中,所以x也是外部活跃节点。SDlist ,它是一个双向链表,其中记录了它的每个儿子的lowpoint。并且,SDlist中的lowpoint值是按照从小到大排序的,为了满足这个条件,我们需要一次排序。因为我们的lowpoint值和深度有关,所以深度大小不会超过n。在求出所有节点的lowpoint后,就可以用计数排序将它们按照从小到大排序,再依次加入它们父亲的SDlist中。那么如何快速的得到lowpoint呢?在深

10、度优先搜索的时候,可以顺便得到它的ecpoint,在深搜返回的时候再将它的ecpoint和它的儿子中最小的lowpoint值做比较中,返回较小的给它父亲。这样就可以用O(n)的时间得到所有的lowpoint和SDlist。当需要知道一个节点的外部活跃信息时,只需要看它的 ecpoint和 SDlist的第一个值是否小于当前处理节点的深度。在以后嵌入回落边的时候,儿子和父亲会合并到一个双连通分量中,那么将该儿子在父亲的SDlist中的值删掉,这样,我们就可以用常数的时间判断一个节点是否是外部活跃节点。和外部活跃节点类似的定义还有相关节点,只要满足下面两个条件它就是相关节点:(1)在处理节点v的时

11、候,该节点处理过,并且和v有直接连接的边。(2)在处理节点v的时候,该节点处理过,并且该节点的子块中存在相关节点。一个节点可以既是外部活跃节点,又是相关节点。和外部活跃节点对立的是内部活跃节点,内部活跃节点就是节点是相关节点,但是不是外部活跃节点。非活跃节点就是既不是外部活跃节点,又不是相关节点。和这些定义对应的还有:外部活跃块:包含外部活跃节点的双连通分量。相关块:包含相关节点的双连通分量内部活跃块:包含相关节点,但是不包含外部活跃节点的双连通分量。非活跃块:不包含外部活跃节点,也不包含相关节点的双连通分量。我们将通过一个叫walkup的函数得到这些信息,后面将具体提到。7反转操作反转操作的

12、目的已经在上文中提到过,那就是为了保证在边的嵌入过程中所有的外部活跃节点都留在外部面上。在图3中,由于y是外部活跃节点,所以在嵌入边前要将下面的双连通分量进行反转,这就是反转操作的形象化体现。那么如何快速的在代码中实现反转操作呢?最简单的方法就是将该块所包含的所有节点的邻接表都颠倒一次。但是这么做太慢了。记得上文所说的根边吗?每个双连通分量都是只有一条根边的,并且根边一定是树边。我们先将所有树边都初始化一个值,为+1,当一个双连通分量需要进行反转操作,那么我们把它的根边上的值变做-1。判断一个当前节点是否被反转只需要知道从根节点只经过树边到达它的路径上有多少个-1,如果是奇数个,那么该节点就需

13、要反转,否则就不需要反转。在处理完所有的节点后,我们只需要进行一次深度优先搜索,便能够知道所有节点的反转信息了。由于我们是按照逆向DFI序处理每个节点,并且所有的外部活跃节点都留在外部面上,这样,我们每次加入回落边的操作都将在外部面上进行。所以我们在处理节点的时候只需要遍历双连通分量的外部面。这个原因导致我们需要对双连通分量的外部面进行单独的记录。因为外部面都是完整的环,所以对每个外部面上的节点,都记录下与它相临的两个外部节点。那么反转操作对外部面的影响又该怎么处理呢?其实对外部面的遍历我们是不需要一直知道到底是逆时针还是顺时针的,我们只需要知道根节点第一次走的是顺时针还是逆时针,然后知道当前

14、节点是由哪一个相邻节点到达的,那么就走向另一个节点。所以当一个双连通分量需要反转的的话,我们只需要交换它的相邻信息。图6对这一操作进行了形象的说明。 图6图6:在图中,我们用黑色和白色的小点来表示方向以便说明。其中白色指顺时针方向,黑色指逆时针方向。在图6(a)中,我们要嵌入边(,4),同时要合并块和 块和 块,其中,方形的节点都是外部活跃节点。在图6(b)中,为了能够将6号节点留在外部面上,我们将下面的块进行了反转。在图6(c)中,我们只需将3的原来指向6,反转后指向4的黑色圆指向2节点,就完成了外部面的反转操作,在3节点不是根节点后它的顺逆时针方向也不再重要。8一个全面的分析在知道了这么多

15、的信息后,下面将对平面嵌入进行一个相对全面的分析。将图GP初始化为一张空图。(接下来的操作是要将图边嵌入到图GP中)首先将按照逆向的DFI序依次处理每个节点。在处理节点v的时候,需要知道它有哪些回落边,当有回落边(v, w)的时候,从w沿着外部面向上走到v,得到相关的一些信息,我们把这个函数叫做walkup, 然后从v向下遍历进行边的嵌入,我们把这个过程叫做 walkdown。如果嵌入不成功,将证明该图是非平面子图,可以找出这个图中阻碍平面嵌入的子图,否则将完成平面嵌入。那么如何在代码中实现边的嵌入呢?这很简单,假若我们需要嵌入边(v, w),因为我们是顺时针记录节点的相邻信息,所以当我们是顺

16、时针遍历的时候就将w节点加入v节点的邻接表头,反之加入邻接表尾。一开始只有树边的情况我们统一加入邻接表头。我们来看看伪代码:(1) 对图进行一次深度优先遍历,并且计算出每个节点的lowpoint。(2) 将图GP初始化,包括为每个节点配备上SDlist,并且将SDlist中的lowpoint排序。(3) For 节点v from n-1 down to 0(按照逆向的DFI序处理每个节点)。(4) For 每个儿子v的儿子c (5) 将根边(, c) 嵌入图GP中(6) For 每个回落边(v, w)(7) Walkup(GP, v, w) /这个函数用来得到一些信息。(8) For v每个v

17、的儿子c (9) Walkdown(GP, ) /这个函数会对回落边进行嵌入。(10) For 每个回落边(v, w)(11) If (, w)不在图GP中 /也就是嵌入失败(12) Return (非平面,GP)(13) RecoverPlanarEmbedding(GP) /得到平面嵌入(14) Return (平面,GP)大家一定注意到了用黑体注明的两个函数,它们都将在接下来有全面的叙述。8.1 walkup函数Walkup函数是用来在处理节点v的时候得到相关信息的。当我们在处理节点v的时候,我们将要嵌入回落边(v, w),那么我们将要从w节点沿着外部面向上遍历到它的祖先v。为了记录相关

18、信息,我们给每个节点配备一个表,叫做proots,记录它有哪些子块是相关块。我们在向上遍历的过程中,当从根节点上升到它的原节点r的时候,我们就将加入r的proots中, 这时候r也成为了相关节点。w在沿着外部面向上遍历的时候可以选择顺时针方向和逆时针方向,并且这两条路径很可能长度差距很大,如果任意选择一个时间消耗会很大。那么如果对两条路径同时进行遍历,最多经过较短路径的两倍长度就可以到达块根节点。(这个操作其实保证了算法的复杂度,在后面的叙述中可以体会到)还有一个问题是:两条回落边(v, w1), (v, w2)导致的遍历中,很可能公共部分是很大的,我们并不想重复的遍历,所以我们给每个节点配备

19、一个visited值,在处理节点v的时候,我们就将所有访问到的节点的visited值置为v(这样在处理其他节点的时候所有节点的visited值都自然清空)。当我们在做向上遍历的时候,访问到一个节点如果visited值为v,那么我们就可以停止对该回落边导致的遍历了。这个操作借助图7给大家做详细的说明。 图7图7:图7中的小点我们借以表示很多的节点,方形的点表示外部活跃节点。图6中首先我们要处理回落边 (v, w),所以我们从w开始沿着两条路同时遍历。遍历到r和c,发现c为根节点,所以停止对该双连通分量的遍历,上升到c,然后遍历到v。接着我们要处理回落边(y, v),我们从y沿两条路向上遍历,通过

20、外部活跃节点z先到达r。然后上升到r,发现r已经访问过,所以终止遍历。INT: Walkup函数的伪码参考附录第2节。8.2 walkdown函数Walkdown是利用相关信息对回落边进行嵌入的函数。大概处理是这样的:类似walkup它也是在外部面上进行操作。但和walkup不同的是,walkdown的遍历中是不能经过外部活跃节点的(有一个特殊情况例外,后面将提到)。在处理节点v的时候,需要从它的每个子块向下进行两次遍历,既顺时针方向遍历和逆时针方向遍历,来嵌入所有的回落边,并且处理嵌入后的影响。在遇到proots 不为空的节点,需要下降到它子块中进行遍历的时候。不过将要重新选择方向。当我们在

21、向下遍历的过程中访问到一个节点,它是外部活跃节点但不是相关节点的时候我们就会终止遍历,所以我们把该节点叫做终止节点。由于终止节点的存在,如果过早的终止,将导致需要嵌入的边没有被嵌入。所以我们的程序必须遵从一个原则:尽量晚的终止遍历。为了这个原则我们遍历的顺序将受到两个法则的约束,这将在下文中提到。当我们遍历到了节点w,我们会遇到这些情况,他们要按顺序判断并且处理,图8会对这些情况有详细的说明:(1) 当节点w是相关节点的时候,我们首先嵌入边(v, w)。(2) 当节点w不是外部活跃节点的时候,我们走向下一个节点。(3) 当节点是外部活跃节点但不是相关节点的时候,我们终止这次遍历。(4) 当节点

22、是一个相关节点并且是关节点的时候,我们向下遍历到它的子块。(这就是唯一可以经过外部活跃节点的情况) 图8图8:图8所示的是在处理节点v的情况下,在左边的图中,w既是一个相关节又是一个外部活跃节点,首先我们将回落边嵌入图GP中。现在它就是一个外部活跃节点但是不是相关节点了,所以就成为了一个终止节点,于是终止遍历。在右边的图中,w依然既是外部活跃节点又是相关节点,但我们在嵌入回落边后,w依然是相关节点。所以它不是一个终止节点,我们下降到它的子块中继续遍历。如果我们在需要下降到子块的时候发现该节点有很多相关子块怎么办呢?我们采取的方式是先下降到内部活跃子块中,由于内部活跃子块中不存在终止节点,所以定

23、会返回到该节点,这时候我们就可以遍历它的其余子快。这就是walkdown执行时必须要遵从的,我们把它叫做法则1。法则1:当下降时遇到多个子块需要遍历,我们总是先下降到内部活跃块中遍历,在返回后再下降到其他的子块中进行遍历。怎样保证法则1呢,我们可以注意到内部活跃子块的lowpoint都是等于v的,而外部活跃子块的lowpoint都是小于v的,所以我们只需要将lowpoint等于v的子块加入原节点proots的表头,然后顺次处理就可以了。那么下降到一个相关外部活跃子块中时,方向又应该怎样选择呢?为了我们的原则,方向选择必须遵从这样的法则:法则2:首先选择能够直接到达内部活跃节点的方向(直接就是不

24、经过其他相关或者活跃节点),然后选择直接到达相关的外部活跃节点的方向。当遍历到终止节点或者根节点的时候,终止遍历。当嵌入边的时候,该边就会覆盖当前所经过的外部面,成为新的外部面的一部分。有了两个法则,我们的遍历就有了约束。那么我们在嵌入完回落边后怎样完成双连通分量的合并呢?在下降到子块的时候,为了后面的合并必须要记录这样几个信息,我们用栈将他们记录下来:非复制点,复制点,下降的方向,返回的方向。合并时这些信息也会合并。之所以要记录下下降和返回的方向是为了处理反转操作的影响,当进入和返回的方向有矛盾的时候,也就是要进行反转操作后再合并。对于反转操作,这里不在累赘的叙述。 为了保证复杂度,我们还有

25、一个提高效率的操作。大家观察图9,其中s是一个外部活跃节点,p是一个内部活跃节点。由于我们的操作都在外部面上进行,所以外部面的遍历将直接关系程序的效率。在图中,我们会先访问到节点p,然后会访问到终止节点s,其中经过的p到s的这段路其实没有什么用,但是它仍然留在外部面上,下一次遍历还会遍历到它。为了避免重复的遍历,我们在遭遇s节点终止的时候连接一条边(v,s),由于这个边其实并不存在,我们把它叫虚边。这样我们在遍历外部面的时候就不会遍历到p到s这一段了。由于每个双连通分量最多遭遇两次终止节点,就最多只有两条虚边。总共也就不会超过2n条虚边,最后我们只需要将这2n条虚边删除就行了。 图9 伪码请参

26、考附录底3节。9复杂度分析我们的算法叙述完了,那么我们实现了我们开始所说的复杂度了吗?这里我们将对它进行一个完整的分析。深度优先遍历和lowpoint的计算是很显然的线形时间处理,为了知道外部活跃信息,我们为每个节点建立的SDlist总体复杂度是O(n),对SDlist中元素的删除操作每次是常数的时间,总体复杂度是O(n)(参考第五节中对SDlist的描述)。反转操作一定只发生在合并双连通分量的时候,所以最多只会有n次反转,并且每次反转也是常数时间的操作。嵌入每条边也是常数时间,总体复杂度也是O(n)。在walkup和walkdown中,我们遍历的的过外部面会因为边的嵌入被更新,从而不在被遍历

27、到。虽然在遍历内部活跃子块的时候不会有嵌入虚边,但是内部活跃子快一定不会被遍历第二次,因为它没有和更早祖先相连的边。所以所有的边都只被遍历了常数次,由于边不会超过3n-5条,加上虚边也是线性的,所以总体复杂度依然是O(n)。这些O(n)的操作都是并行的,所以我们的总体复杂度就是O(n)!我们实现了O(n)的时间复杂度,并且编程复杂度较简单!10. 正确性的证明如果算法根本就是错的,那么怎样的简单都是没用的。所以这一节也很重要。除了一种特殊情况,我们在walkdown中是不会经过外部活跃节点的,所以在嵌入边的过程中不会覆盖外部活跃节点,如果所有得外部活跃节点都留在了外部面上。那么也就不会出现交叉

28、的边。那么那种特殊情况会不会覆盖外部活跃节点呢?其实不会的,在情况2的证明中说明了这个问题。还记得文章开始我们提到的k5图和k33图吗。对于是非平面图,我们只需要证明该图中存在k5或者k33的同胚图就可以了。我们将依次讨论失败的情况,逐个找出他们构成的k33或者k5图。情况1:当节点有三个或者以上的外部活跃相关子块的时。前面提到对于一个节点,由于我们只进行顺时针和逆时针两次遍历,当遍历一个外部活跃子块的时候一定会终止,那么最多遍历两个外部活跃子块我们的遍历就结束了。所以一旦节点包含了3个外部活跃子块,嵌入就失败了。不过在这样的图中,我们是可以找到一张k33图的。如图10,图中我们正处理节点v, u是节点v的祖先节点。a , b , c是r的儿子,也代表了它拥有的三个外部活跃相关子块。其中(u, v , r),(

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1