【问题标题】:Adjacency or Edge List to Faces面的邻接或边列表
【发布时间】:2017-10-08 12:43:16
【问题描述】:

如何从平面图的边表或邻接表表示转到面表?

例如,这张图(它不是 0 索引,很奇怪):

我想要一个看起来像这样的列表:

[
[1,2,8,7],
[1,2,4,3],
[1,3,5,7],
[2,4,6,8],
[5,6,7,8],
[3,4,6],
[3,5,6]
]

它不必是那种格式或顺序,但它应该是所有面孔的某种列表(或集合)。包括外面。

对于具有 V 个顶点的图(E=O(V),因为是平面图),算法应该在 O(V) 中生成此列表。

【问题讨论】:

  • 邻接表是按角度排序的吗?如果不是,他们会是吗?

标签: algorithm graph polygons face planar-graph


【解决方案1】:

您需要生成图形的平面嵌入。一个问题是,通常一个双连通图可以生成多个平面嵌入。

"Planarity Testing by Path Addition" PhD thesis 中给出了一种平面性测试和嵌入算法,这解决了生成图的所有可能平面嵌入的问题(在 O(V+E) 时间和内存中,用于双连接图的单个嵌入) V 个顶点和 E 个边,并在 O(P(V+E)) 时间和 O(V+E) 内存中生​​成所有可能的唯一平面嵌入,其中 P 是嵌入的排列数。

第 5 章详细介绍了测试图形的平面性以及为每个顶点生成循环边顺序所需的算法(以及如何迭代到嵌入的下一个排列)。

给定一个循环边顺序,您可以通过获取每条边并在到达每个连续顶点时跟随循环边顺序中的下一个顺时针(或逆时针)边来生成图形的面。

【讨论】:

  • 我认为op指定了输入图保证是平面的,所以我们不必测试它。
  • @Yadli 几乎所有的平面性测试都倾向于通过构建图形的平面嵌入来测试平面性——这比寻找与 K5 或 K3,3 同构的子图要有效得多。因此,如果您想获得图形的平面嵌入,您可以通过平面性测试算法运行它 - 您可以忽略测试部分,因为您只需要嵌入。
  • 感谢您的澄清。我知道嵌入算法比寻找禁止模式有更好的测试平面性的方法,但我的意思是,我快速浏览了你引用的论文,它会列举所有嵌入。我们可以轻松地构建具有指数级嵌入的平面图(您的答案中的 O(P(V+E)) 部分,我认为这不是多项式)。是不是在找到第一个embedding的那一刻就break,或者embeddings是并行构造的,只能比非多项式部分晚才能得到结果?
  • @Yadli 生成所有嵌入的机制是使用 O(V+E) 时间和内存中的第一个嵌入创建的。然后迭代到嵌入的每个连续排列需要 O(KV+KE) 时间(并且没有额外的内存),其中 K 平均为一个常数,当考虑到排列的总数时(有关时间复杂度的详细分解,请参见第 94 页Steinhaus-Johnson-Trotter 排列算法用于有效地迭代所有排列)。因此,对于 P 个排列,该算法将花费 O(PV+PE) 时间。
【解决方案2】:

正如@gilleain 在他的回答中提到的那样,您必须实际布局图表,因为如果您只给出拓扑结构,可能会有多种布局。这里我会给出一些算法分析,然后给出一个直截了当的解决方案。

引理 1:一条边涉及至少一个面,最多两个面 脸。

证明:我们在二维空间中绘图,所以一条线将平面分成两半。

这意味着我们可以为每条边附加一个“容量”,初始化为 2。当我们找到包含一条边的面时,我们从中减去 1。

由于人脸是平面图中的一个循环,因此问题转向在上述约束条件下找到循环。

引理 2:如果我们检查两个有效解决方案之间的差异,它们在具有互补容量(1 对 2 和 2 对 1)的边上是不同的。

证明:见图。

因此,当您在寻找解决方案时耗尽边缘的容量时,您会自动排除导致另一个解决方案的歧义。

因此,直接的解决方案是在图中进行 DFS 以查找循环。当你找到一个时,减去相关边的容量。当容量达到 0 时,考虑进一步 DFS 时移除边缘。 注意,当找到循环时,我们必须检查其中的所有边是否容量为1。如果是,则必须跳过此循环,因为将这个循环包含在我们的结果中会导致重复计数。

def DFS-Tarjan(v, capacities, stack)
    for e in N(v):
        if capacity[e] != 0:
            if stack.contains[e.target] AND NOT all of them have capacity 1:
                output-loop(stack, e.target)
                for e2 in stack:
                    capacity[e2] -= 1
            else:
                stack.push(e.target)
                DFS-Tarjan(v, capacities, stack)
                stack.pop(e.target)
        else:
            pass # capacity drained

def find-faces(g):
    initialize capacities of g.E to [2...2]
    for v in unvisited(g.V):
        DFS-Tarjan(v, capacities, [])

如果您更改 DFS 的顺序,可以找到多种解决方案。对于单个解决方案,算法是 O(V),因为每条边的消耗不超过两次。

【讨论】:

  • 有趣的答案。我不确定图表 - 颜色是什么意思?另外-也许您知道这一点,但是-在答案正文中将“@”放在用户名之前不会做任何事情:)
  • @gilleain 是的,我只是希望有一天他们会在答案文本中添加“@”功能。在图中,green=edges for face 1,red=edges for face 2。对于中间的图形,两个面似乎重叠,但通过调整布局(将底部顶点移动到顶部)我们可以看到这是仍然是一个有效的解决方案——我认为这是这个问题有趣的部分原因:)。
  • 通过 DFS 寻找循环并不等同于寻找面孔。您可以在 DFS 中找到一个循环,然后将定义为在该循环内的区域被 DFS 内的其他路径一分为二,这样这些面实际上就是由原始循环和二等分定义的两个子区域。这可以通过连续的子区域重复。
  • @MT0 请参见引理 2。虽然我们可以计算更大的外部循环,但该算法确保它不会进一步计算 ALL 内部循环更大的循环。因此通过调整布局,我们将较大的循环变小,将较小的循环变大,使其看起来像两张不相交的脸。
  • 请在您的解释中详细说明很多,因为它目前似乎没有意义,并且非常不清楚您的“引理2”中证明了什么。您似乎严重依赖 DFS 处理边缘的顺序 - 如果这是不确定的,那么如果没有大量准备工作重新排序边缘的优先级,那么您的算法在一般情况下就无法工作。跨度>
【解决方案3】:

简短的回答是:您必须实际布置图表!更准确地说,您必须在平面中找到图形的嵌入 - 假设存在一个没有边交叉的图形。

所以,您上面的嵌入是:

1: [2, 7, 3]
2: [1, 4, 8]
3: [1, 5, 6, 4]
...

每个顶点在其邻居集上都有一个排序。您必须指定该顺序是顺时针还是逆时针,否则就是全部。

一旦你有了嵌入,就可以使用combinatorial map 恢复人脸。尽管它确实涉及飞镖(或标志),但这看起来比实际更棘手。

首先,将每条边分成标志(顶点 + 半边)并进行排列(wiki 描述中的 sigma)来存储地图。例如,我们可以按照与地图相同的顺序标记标志 - 然后 1: [2, 7, 3] 变为 {1->2 : 1, 1->7 : 2, 1->3 : 3} 和以此类推。

例如,一个立方体(注意:去掉了中间的边缘!):

然后计算 alpha(对合置换),它只是将标志映射到边缘上的另一个标志。最后,phi 是这两个排列的乘积,phi 的循环为您提供了面孔。

所以,根据图像中的 phi,我们有 (1, 6, 24, 19) 是外表面(注意这些是飞镖,所以我们考虑它开始的顶点)。

【讨论】:

    猜你喜欢
    • 2018-04-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-07
    • 2017-08-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多