算法合集之《平面嵌入》.docx

上传人:b****3 文档编号:3849465 上传时间:2022-11-25 格式:DOCX 页数:17 大小:142.25KB
下载 相关 举报
算法合集之《平面嵌入》.docx_第1页
第1页 / 共17页
算法合集之《平面嵌入》.docx_第2页
第2页 / 共17页
算法合集之《平面嵌入》.docx_第3页
第3页 / 共17页
算法合集之《平面嵌入》.docx_第4页
第4页 / 共17页
算法合集之《平面嵌入》.docx_第5页
第5页 / 共17页
点击查看更多>>
下载资源
资源描述

算法合集之《平面嵌入》.docx

《算法合集之《平面嵌入》.docx》由会员分享,可在线阅读,更多相关《算法合集之《平面嵌入》.docx(17页珍藏版)》请在冰豆网上搜索。

算法合集之《平面嵌入》.docx

算法合集之《平面嵌入》

平面嵌入

四川绵阳南山中学古楠

[引子]

什么是平面嵌入呢?

大家还记得冬令营2005的蜂窝玉米吗?

它涉及到了图的一个性质,那就是所有的边都不能相交。

图这个性质叫做图的平面性。

当我们需要知道一个图是否具有平面性和这张图实现平面性后的结构时,平面嵌入算法就是一个好的工具。

[摘要]

本文主要由两大部分构成。

第一部分主要介绍了平面嵌入的算法,其中包括了具体的操作,复杂度的分析,正确性的证明和一些相关题目。

第二部分主要是附录,包含参考文献和伪码。

[关键字]

平面,嵌入,内部活跃,相关,外部活跃,块,根边,回落边

[目录]

一.算法--------------------------------------------------1

1.平面嵌入的相关定义----------------------------------1

2.算法的目的------------------------------------------2

3.一些相关的知识--------------------------------------2

4.算法总揽--------------------------------------------2

5.边的嵌入--------------------------------------------3

6.外部活跃,相关与内部活跃----------------------------3

7.反转操作--------------------------------------------5

8.一个全面的分析--------------------------------------6

8.1walkup函数-------------------------------------7

8.2walkdown函数-----------------------------------8

9.复杂度的分析----------------------------------------9

10.正确性的证明---------------------------------------10

11.相关题目-------------------------------------------12

12.总结-----------------------------------------------12

二.附录--------------------------------------------------12

1.MergeBiconnectedComponent伪码----------------------13

2.walkup伪码------------------------------------------13

3.walkdown伪码----------------------------------------14

[算法]

1.平面嵌入的相关定义

如果对平面嵌入还有些陌生,希望下面的定义对你有所帮助。

(1)平面作图:

一张图能够转化会为一张所有边都不相交的图(在节点上相交不算),转化过程就叫做平面作图。

(原来相连的节点在转化后依然相连,图1展示了平面作图)

(2)平面图:

一张图能够进行平面作图它就是平面图,否则为非平面图。

(3)平面嵌入:

和平面作图是等价的,不过在储存方式上是这样的,对于每个节点都顺时针储存和它连接节点。

我们将记录用的表叫邻接表。

注意:

本篇论文所有的平面都是指这里的相关定义,和几何平面是不同的。

图1

2.算法的目的

在明白了平面嵌入的一些基本定义以后。

对于给定的图G,它有n个节点,m条边。

(以后我们将始终用n表示图G的节点个数,m表示图G的节点边数)算法的目的就是用O(n)的时间判断一个图是否为平面图,如果是的话要用O(n)的时间实现平面嵌入。

3.一些相关的知识

为了实现我们的目的,我们必须先知道一些必要的知识。

这些知识将包含深度优先搜索,双连通分量,关节点,计数排序(一个复杂度和关键码范围有关的算法)。

还要知道一些必要的定理,如一个平面图的边将不能超过3n-5条边,否则它将是一个非平面图。

Kuratowiski曾经证明了两个图将阻碍平面嵌入的进行,那就是图2所描述的图,我们称作k5图和k33图。

一张图中如果包含了k5图和k33图的同胚图(如果一个图可以通过延展,弯曲,合并连接节点的方式转化为另一张图,这两张图就是同胚的),那么这张图一定是非平面图,反之也成立。

我们的算法有一个基本操作,就是加边操作。

我们创建一个图GP,我们要将原图中的边按照一定的顺序加入图GP中。

我们把这个操作称作边的嵌入。

为了叙述的通顺性,有关复杂度的分析和正确性的证明我都会放到论文的末尾。

图2

4.算法总览

为了后面的叙述更加清晰,这里将叙述算法的总体过程。

我们只讨论对双连通分量图的平面嵌入,对于有关节点的图,我们可以将图从关节点处断开分别进行平面嵌入后再合并。

首先对图进行一次深度优先遍历,得到一棵深度优先搜索树和图的逆向深度优先搜索序(由于深度优先搜索序简称DFI序,所以后面写作逆向DFI序)。

我们将每个节点和它儿子相连的边叫做树边,将和它后裔相连的边叫做回落边(注意:

我们指的后裔是不包括儿子的)。

然后我们按照逆向的DFI序依次处理每个节点。

所谓处理节点就是:

首先将它的树边嵌入图GP中,然后将它的的回落边都嵌入到图GP中,我们并不处理它和它祖先相连的边(祖先是包括父亲的)。

如果嵌入过程失败,那么可以得到图G是非平面图的结论,否则就完成构造。

5.边的嵌入

我们算法最重要和操作就是对边的嵌入,我将针对图3进行详细的说明,在图3(a)中,我们要嵌入边(v,w)。

在开始的时候该连通分量包含了一个关节点r,我们将r去掉后该连通分量就变成了两个部分,就象图3(b)。

然后分别给两个分量配上一个r,这样r就被分成了两个,象图3(c)。

然后将边(v,w)嵌入,然后将两个连通分量合并在一起,这时候,r已经不再是关节点了,象图3(d)。

这样我们就完成了一个最基本的嵌入操作。

为什么要这么麻烦呢?

大家注意到图3(d)了吗?

它的下半部分左右颠倒了,这也是嵌入时候的一个操作,叫做反转操作,这个操作是为了保证图GP的一个重要性质,这些都将在下文中提到。

图3

在我们的嵌入过程中,只要图GP中含有关节点,我们都将从这个关节点把它所在的连通分量断为两个部分。

在嵌入回落边的时候又会将他们组合在一起。

这样,图GP就是由很多的双连通分量组成的,请大家注意,在代码的实现中我们必须实际的这样处理,并不是为了理解。

6.外部活跃,相关与内部活跃

反转操作所要保护的性质就是:

在嵌入边的过程中所有的外部活跃节点都必须留在外部面上。

什么是外部面呢?

外部面的名称很形象,也就是图中接触到最外层空间的点和边构成的面,并且外部面都会是完整的环,如果一个双连通分量只有一条边,那么我们就要进行相应的转化,像图4这样。

图4

外部活跃节点的定义就要复杂些,为了清楚的叙述,我们要加入些新的定义。

我们将双连通分量中DFI序最小的节点称作该双连通分量的根节点。

由于我们在只有嵌入树边的时候才会产生分离操作,所以我们会发现当一个关节点被分离成多个后,和儿子分到一个分量中的节点一定会成为该分量的根节点。

我们把这些节点称作复制点,把唯一的不是根的点称作原节点。

假设r是一个双连通分量B的根节点,那么r在该双连通分量中一定只有一个儿子,若r有两个儿子c1,c2,由于是深度优先遍历,在访问到c1后,继续访问的过程中将通过双连通分量B的某一条路径不经过r到达c2,那么c2将不会成为r的儿子,也就与假设矛盾。

我们设定r的这个唯一儿子为c,那么我们将r到c的边称做根边(由于我们只有在加入树边的时候才会导致双连通分量的拆分,所以根边一定是树边),将该根节点称作

节点(儿子还没有确定的我们写作r’),将该双连通分量称做

块。

这样就可以和原r节点区分开并且双连通分量也可以明确表示(后来的叙述都将遵从这里的设定)。

子块的定义在这里就产生了,我们将

块称做原节点r的子块。

知道了这些后,只要满足下面两个条件的任何一个,它就是外部活跃节点:

(1)当我们在处理节点v的时候,该节点处理过,并且有直接连接v的祖先的边。

(2)当我们在处理节点v的时候,该节点处理过,并且该节点的子块中存在外部活跃节点。

就象图5中,方形的点都是外部活跃节点。

那么我们怎么得到外部活跃节点的信息呢?

我们把每个节点本身能够直接到达的最早祖先的深度记做该节点的ecpoint,把能够直接或者间接到达的最早祖先的深度叫做该接点的lowpoint(所谓间接就是通过它的子孙到达)。

同时,我们给每个节点配备一个

图5

图5:

z有直接连接v的祖先u的边所以,v是外部活跃节点,,z在d的子块中,所以d也是外部活跃节点,d在x的子块中,所以x也是外部活跃节点。

SDlist,它是一个双向链表,其中记录了它的每个儿子的lowpoint。

并且,SDlist中的lowpoint值是按照从小到大排序的,为了满足这个条件,我们需要一次排序。

因为我们的lowpoint值和深度有关,所以深度大小不会超过n。

在求出所有节点的lowpoint后,就可以用计数排序将它们按照从小到大排序,再依次加入它们父亲的SDlist中。

那么如何快速的得到lowpoint呢?

在深度优先搜索的时候,可以顺便得到它的ecpoint,在深搜返回的时候再将它的ecpoint和它的儿子中最小的lowpoint值做比较中,返回较小的给它父亲。

这样就可以用O(n)的时间得到所有的lowpoint和SDlist。

当需要知道一个节点的外部活跃信息时,只需要看它的ecpoint和SDlist的第一个值是否小于当前处理节点的深度。

在以后嵌入回落边的时候,儿子和父亲会合并到一个双连通分量中,那么将该儿子在父亲的SDlist中的值删掉,这样,我们就可以用常数的时间判断一个节点是否是外部活跃节点。

和外部活跃节点类似的定义还有相关节点,只要满足下面两个条件它就是相关节点:

(1)在处理节点v的时候,该节点处理过,并且和v有直接连接的边。

(2)在处理节点v的时候,该节点处理过,并且该节点的子块中存在相关节点。

注意:

一个节点可以既是外部活跃节点,又是相关节点。

和外部活跃节点对立的是内部活跃节点,内部活跃节点就是节点是相关节点,但是不是外部活跃节点。

非活跃节点就是既不是外部活跃节点,又不是相关节点。

和这些定义对应的还有:

外部活跃块:

包含外部活跃节点的双连通分量。

相关块:

包含相关节点的双连通分量

内部活跃块:

包含相关节点,但是不包含外部活跃节点的双连通分量。

非活跃块:

不包含外部活跃节点,也不包含相关节点的双连通分量。

我们将通过一个叫walkup的函数得到这些信息,后面将具体提到。

7.反转操作

反转操作的目的已经在上文中提到过,那就是为了保证在边的嵌入过程中所有的外部活跃节点都留在外部面上。

在图3中,由于y是外部活跃节点,所以在嵌入边前要将下面的双连通分量进行反转,这就是反转操作的形象化体现。

那么如何快速的在代码中实现反转操作呢?

最简单的方法就是将该块所包含的所有节点的邻接表都颠倒一次。

但是这么做太慢了。

记得上文所说的根边吗?

每个双连通分量都是只有一条根边的,并且根边一定是树边。

我们先将所有树边都初始化一个值,为+1,当一个双连通分量需要进行反转操作,那么我们把它的根边上的值变做-1。

判断一个当前节点是否被反转只需要知道从根节点只经过树边到达它的路径上有多少个-1,如果是奇数个,那么该节点就需要反转,否则就不需要反转。

在处理完所有的节点后,我们只需要进行一次深度优先搜索,便能够知道所有节点的反转信息了。

由于我们是按照逆向DFI序处理每个节点,并且所有的外部活跃节点都留在外部面上,这样,我们每次加入回落边的操作都将在外部面上进行。

所以我们在处理节点的时候只需要遍历双连通分量的外部面。

这个原因导致我们需要对双连通分量的外部面进行单独的记录。

因为外部面都是完整的环,所以对每个外部面上的节点,都记录下与它相临的两个外部节点。

那么反转操作对外部面的影响又该怎么处理呢?

其实对外部面的遍历我们是不需要一直知道到底是逆时针还是顺时针的,我们只需要知道根节点第一次走的是顺时针还是逆时针,然后知道当前节点是由哪一个相邻节点到达的,那么就走向另一个节点。

所以当一个双连通分量需要反转的的话,我们只需要交换它的相邻信息。

图6对这一操作进行了形象的说明。

图6

图6:

在图中,我们用黑色和白色的小点来表示方向以便说明。

其中白色指顺时针方向,黑色指逆时针方向。

在图6(a)中,我们要嵌入边(

,4),同时要合并块和

块和

块,其中,方形的节点都是外部活跃节点。

在图6(b)中,为了能够将6号节点留在外部面上,我们将下面的块进行了反转。

在图6(c)中,我们只需将3的原来指向6,反转后指向4的黑色圆指向2节点,就完成了外部面的反转操作,在3节点不是根节点后它的顺逆时针方向也不再重要。

8.一个全面的分析

在知道了这么多的信息后,下面将对平面嵌入进行一个相对全面的分析。

将图GP初始化为一张空图。

(接下来的操作是要将图边嵌入到图GP中)

首先将按照逆向的DFI序依次处理每个节点。

在处理节点v的时候,需要知道它有哪些回落边,当有回落边(v,w)的时候,从w沿着外部面向上走到v,得到相关的一些信息,我们把这个函数叫做walkup,然后从v向下遍历进行边的嵌入,我们把这个过程叫做walkdown。

如果嵌入不成功,将证明该图是非平面子图,可以找出这个图中阻碍平面嵌入的子图,否则将完成平面嵌入。

那么如何在代码中实现边的嵌入呢?

这很简单,假若我们需要嵌入边(v,w),因为我们是顺时针记录节点的相邻信息,所以当我们是顺时针遍历的时候就将w节点加入v节点的邻接表头,反之加入邻接表尾。

一开始只有树边的情况我们统一加入邻接表头。

我们来看看伪代码:

(1)对图进行一次深度优先遍历,并且计算出每个节点的lowpoint。

(2)将图GP初始化,包括为每个节点配备上SDlist,并且将SDlist中的lowpoint排序。

(3)For节点vfromn-1downto0(按照逆向的DFI序处理每个节点)。

(4)For每个儿子v的儿子c

(5)将根边(

c)嵌入图GP中

(6)For每个回落边(v,w)

(7)Walkup(GP,v,w)//这个函数用来得到一些信息。

(8)Forv每个v的儿子c

(9)Walkdown(GP,

)//这个函数会对回落边进行嵌入。

(10)For每个回落边(v,w)

(11)If(

w)不在图GP中//也就是嵌入失败

(12)Return(非平面,GP)

(13)RecoverPlanarEmbedding(GP)//得到平面嵌入

(14)Return(平面,GP)

大家一定注意到了用黑体注明的两个函数,它们都将在接下来有全面的叙述。

8.1walkup函数

Walkup函数是用来在处理节点v的时候得到相关信息的。

当我们在处理节点v的时候,我们将要嵌入回落边(v,w),那么我们将要从w节点沿着外部面向上遍历到它的祖先v。

为了记录相关信息,我们给每个节点配备一个表,叫做proots,记录它有哪些子块是相关块。

我们在向上遍历的过程中,当从根节点

上升到它的原节点r的时候,我们就将

加入r的proots中,这时候r也成为了相关节点。

w在沿着外部面向上遍历的时候可以选择顺时针方向和逆时针方向,并且这两条路径很可能长度差距很大,如果任意选择一个时间消耗会很大。

那么如果对两条路径同时进行遍历,最多经过较短路径的两倍长度就可以到达块根节点。

(这个操作其实保证了算法的复杂度,在后面的叙述中可以体会到)

还有一个问题是:

两条回落边(v,w1),(v,w2)导致的遍历中,很可能公共部分是很大的,我们并不想重复的遍历,所以我们给每个节点配备一个visited值,在处理节点v的时候,我们就将所有访问到的节点的visited值置为v(这样在处理其他节点的时候所有节点的visited值都自然清空)。

当我们在做向上遍历的时候,访问到一个节点如果visited值为v,那么我们就可以停止对该回落边导致的遍历了。

这个操作借助图7给大家做详细的说明。

图7

图7:

图7中的小点我们借以表示很多的节点,方形的点表示外部活跃节点。

图6中首先我们要处理回落边(v,w),所以我们从w开始沿着两条路同时遍历。

遍历到r和c’,发现c’为根节点,所以停止对该双连通分量的遍历,上升到c,然后遍历到v’。

接着我们要处理回落边(y,v’),我们从y沿两条路向上遍历,通过外部活跃节点z先到达r’。

然后上升到r,发现r已经访问过,所以终止遍历。

INT:

Walkup函数的伪码参考附录第2节。

8.2walkdown函数

Walkdown是利用相关信息对回落边进行嵌入的函数。

大概处理是这样的:

类似walkup它也是在外部面上进行操作。

但和walkup不同的是,walkdown的遍历中是不能经过外部活跃节点的(有一个特殊情况例外,后面将提到)。

在处理节点v的时候,需要从它的每个子块向下进行两次遍历,既顺时针方向遍历和逆时针方向遍历,来嵌入所有的回落边,并且处理嵌入后的影响。

在遇到proots不为空的节点,需要下降到它子块中进行遍历的时候。

不过将要重新选择方向。

当我们在向下遍历的过程中访问到一个节点,它是外部活跃节点但不是相关节点的时候我们就会终止遍历,所以我们把该节点叫做终止节点。

由于终止节点的存在,如果过早的终止,将导致需要嵌入的边没有被嵌入。

所以我们的程序必须遵从一个原则:

尽量晚的终止遍历。

为了这个原则我们遍历的顺序将受到两个法则的约束,这将在下文中提到。

当我们遍历到了节点w,我们会遇到这些情况,他们要按顺序判断并且处理,图8会对这些情况有详细的说明:

(1)当节点w是相关节点的时候,我们首先嵌入边(v,w)。

(2)当节点w不是外部活跃节点的时候,我们走向下一个节点。

(3)当节点是外部活跃节点但不是相关节点的时候,我们终止这次遍历。

(4)当节点是一个相关节点并且是关节点的时候,我们向下遍历到它的子块。

(这就是唯一可以经过外部活跃节点的情况)

图8

图8:

图8所示的是在处理节点v的情况下,在左边的图中,w既是一个相关节又是一个外部活跃节点,首先我们将回落边嵌入图GP中。

现在它就是一个外部活跃节点但是不是相关节点了,所以就成为了一个终止节点,于是终止遍历。

在右边的图中,w依然既是外部活跃节点又是相关节点,但我们在嵌入回落边后,w依然是相关节点。

所以它不是一个终止节点,我们下降到它的子块中继续遍历。

如果我们在需要下降到子块的时候发现该节点有很多相关子块怎么办呢?

我们采取的方式是先下降到内部活跃子块中,由于内部活跃子块中不存在终止节点,所以定会返回到该节点,这时候我们就可以遍历它的其余子快。

这就是walkdown执行时必须要遵从的,我们把它叫做法则1。

法则1:

当下降时遇到多个子块需要遍历,我们总是先下降到内部活跃块中遍历,在返回后再下降到其他的子块中进行遍历。

怎样保证法则1呢,我们可以注意到内部活跃子块的lowpoint都是等于v的,而外部活跃子块的lowpoint都是小于v的,所以我们只需要将lowpoint等于v的子块加入原节点proots的表头,然后顺次处理就可以了。

那么下降到一个相关外部活跃子块中时,方向又应该怎样选择呢?

为了我们的原则,方向选择必须遵从这样的法则:

法则2:

首先选择能够直接到达内部活跃节点的方向(直接就是不经过其他相关或者活跃节点),然后选择直接到达相关的外部活跃节点的方向。

当遍历到终止节点或者根节点的时候,终止遍历。

当嵌入边的时候,该边就会覆盖当前所经过的外部面,成为新的外部面的一部分。

有了两个法则,我们的遍历就有了约束。

那么我们在嵌入完回落边后怎样完成双连通分量的合并呢?

在下降到子块的时候,为了后面的合并必须要记录这样几个信息,我们用栈将他们记录下来:

非复制点,复制点,下降的方向,返回的方向。

合并时这些信息也会合并。

之所以要记录下下降和返回的方向是为了处理反转操作的影响,当进入和返回的方向有矛盾的时候,也就是要进行反转操作后再合并。

对于反转操作,这里不在累赘的叙述。

为了保证复杂度,我们还有一个提高效率的操作。

大家观察图9,其中s是一个外部活跃节点,p是一个内部活跃节点。

由于我们的操作都在外部面上进行,所以外部面的遍历将直接关系程序的效率。

在图中,我们会先访问到节点p,然后会访问到终止节点s,其中经过的p到s的这段路其实没有什么用,但是它仍然留在外部面上,下一次遍历还会遍历到它。

为了避免重复的遍历,我们在遭遇s节点终止的时候连接一条边(v,s),由于这个边其实并不存在,我们把它叫虚边。

这样我们在遍历外部面的时候就不会遍历到p到s这一段了。

由于每个双连通分量最多遭遇两次终止节点,就最多只有两条虚边。

总共也就不会超过2n条虚边,最后我们只需要将这2n条虚边删除就行了。

图9

伪码请参考附录底3节。

9.复杂度分析

我们的算法叙述完了,那么我们实现了我们开始所说的复杂度了吗?

这里我们将对它进行一个完整的分析。

深度优先遍历和lowpoint的计算是很显然的线形时间处理,为了知道外部活跃信息,我们为每个节点建立的SDlist总体复杂度是O(n),对SDlist中元素的删除操作每次是常数的时间,总体复杂度是O(n)(参考第五节中对SDlist的描述)。

反转操作一定只发生在合并双连通分量的时候,所以最多只会有n次反转,并且每次反转也是常数时间的操作。

嵌入每条边也是常数时间,总体复杂度也是O(n)。

在walkup和walkdown中,我们遍历的的过外部面会因为边的嵌入被更新,从而不在被遍历到。

虽然在遍历内部活跃子块的时候不会有嵌入虚边,但是内部活跃子快一定不会被遍历第二次,因为它没有和更早祖先相连的边。

所以所有的边都只被遍历了常数次,由于边不会超过3n-5条,加上虚边也是线性的,所以总体复杂度依然是O(n)。

这些O(n)的操作都是并行的,所以我们的总体复杂度就是O(n)!

我们实现了O(n)的时间复杂度,并且编程复杂度较简单!

10.正确性的证明

如果算法根本就是错的,那么怎样的简单都是没用的。

所以这一节也很重要。

除了一种特殊情况,我们在walkdown中是不会经过外部活跃节点的,所以在嵌入边的过程中不会覆盖外部活跃节点,如果所有得外部活跃节点都留在了外部面上。

那么也就不会出现交叉的边。

那么那种特殊情况会不会覆盖外部活跃节点呢?

其实不会的,在情况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