【问题标题】:Position N circles of different radii inside a larger circle without overlapping将N个不同半径的圆定位在一个更大的圆内而不重叠
【发布时间】:2010-10-05 14:26:44
【问题描述】:

给定 n 个半径为 r1 ... rn 的圆,将它们定位为没有圆重叠且边界圆的半径为“小”。

程序将列表 [r1, r2, ... rn] 作为输入并输出圆心。

  1. 我要求“小”,因为“最小”半径将其转换为更困难的问题(最小版本已被证明是 NP 硬/完整 - 请参见问题结尾附近的脚注)。我们不需要最低限度。如果圆圈形成的形状看起来相当圆形,那就足够了。
  2. 如果有帮助,您可以假设 Rmax/Rmin
  3. 低优先级问题 - 程序应该能够处理 2000 多个圈子。一开始,100-200 圈应该没问题。
  4. 您可能已经猜到,圆圈不需要紧紧地挤在一起,甚至不需要相互接触。

我们的目的是对给定的圆圈进行视觉上令人愉悦的排列,这些圆圈可以放在更大的圆圈内,并且不会留下太多的空白空间。 (如color blindness test picture 中的圆圈)。

您可以使用下面的 Python 代码作为起点(此代码需要 numpy 和 matplotlib - Linux 上的“sudo apt-get install numpy matplotlib”)...

import pylab
from matplotlib.patches import Circle
from random import gauss, randint
from colorsys import hsv_to_rgb

def plotCircles(circles):
    # input is list of circles
    # each circle is a tuple of the form (x, y, r)
    ax = pylab.figure()
    bx = pylab.gca()
    rs = [x[2] for x in circles]
    maxr = max(rs)
    minr = min(rs)
    hue = lambda inc: pow(float(inc - minr)/(1.02*(maxr - minr)), 3)

    for circle in circles:
        circ = Circle((circle[0], circle[1]), circle[2])
        color = hsv_to_rgb(hue(circle[2]), 1, 1)
        circ.set_color(color)
        circ.set_edgecolor(color)
        bx.add_patch(circ)
    pylab.axis('scaled')
    pylab.show()

def positionCircles(rn):
    # You need rewrite this function
    # As of now, this is a dummy function
    # which positions the circles randomly
    maxr = int(max(rn)/2)
    numc = len(rn)
    scale = int(pow(numc, 0.5))
    maxr = scale*maxr

    circles = [(randint(-maxr, maxr), randint(-maxr, maxr), r)
               for r in rn]
    return circles

if __name__ == '__main__':
    minrad, maxrad = (3, 5)
    numCircles = 400

    rn = [((maxrad-minrad)*gauss(0,1) + minrad) for x in range(numCircles)]

    circles = positionCircles(rn)
    plotCircles(circles)

补充说明:谷歌搜索结果中常用的圈包算法不适用于此问题。

因此,另一个“圆包装算法”的问题陈述是:给定一个复数 K(在这种情况下,图称为单纯复形,或简称为复数)和适当的边界条件,计算相应圆包装的半径K....

它基本上从一个图表开始,说明哪些圆相互接触(图的顶点表示圆,边表示圆之间的接触/切线关系)。必须找到圆的半径和位置,以满足图形表示的接触关系。

另一个问题确实有一个有趣的观察(独立于这个问题):

圆包装定理 - 每个圆包装都有一个对应的平面图(这是容易/显而易见的部分),每个平面图都有一个对应的圆包装(不太明显的部分)。图和包装是对偶的,并且是唯一的。

在我们的问题中,我们没有平面图或切线关系。

这篇论文 - Robert J. Fowler、Mike Paterson、Steven L. Tanimoto:平面中的最优包装和覆盖是 NP 完全的 - 证明了这个问题的最小版本是 NP 完全的.但是,该论文无法在线获得(至少不容易获得)。

【问题讨论】:

  • 不是难题...这是我在工作中一直在努力解决的问题。我有一个基本的解决方案,但我正在寻找更好的解决方案。
  • 在您发表评论后,我再次查看了我的问题,但没有看到拼图部分。请让我知道问题是否以暗示它是一个不严重的难题类型问题的方式出现。我会改写它。或者,你的意思是说这对于堆栈溢出来说太难了?
  • @Greg:我认为它非常主观。你所说的谜题有些人会称之为编程相关,而另一些人会称之为数学。对于我的一个问题,我已经经历过这种类型的争论。
  • 没关系,我只有一票。
  • @Greg Hewgill:他正在寻找一个 NP 难题的近似值。如果不在编程/计算机科学问答网站上,他还应该问什么??

标签: algorithm language-agnostic geometry


【解决方案1】:

不是一个解决方案,只是一个集思广益的想法:IIRC 获得 TSP 近似解决方案的一种常见方法是从随机配置开始,然后应用本地操作(例如“交换”路径中的两条边)尝试并获得越来越短的路径。 (Wikipedia link)

我认为这里可能会出现类似的情况:

  1. 从随机中心位置开始
  2. “优化”这些位置,因此没有重叠的圆圈,因此圆圈尽可能靠近,方法是增加重叠圆圈之间的距离并减少其他圆圈之间的距离,直到它们紧密排列。这可以通过某种能量最小化来完成,或者可能有更有效的贪婪解决方案。
  3. 将迭代改进算子应用于中心位置
  4. 转到 2,在最大迭代次数后中断,或者如果最后一次迭代没有发现任何改进

有趣的问题是:您可以在第 3 步中使用哪种“迭代改进算子”?我们可以假设该阶段的位置是局部最优的,但可以通过重新排列大部分圆圈来改善它们。我的建议是在圆圈中任意选择一条线。然后取线“左边”的所有圆圈,并将它们镜像到垂直于该线的某个轴上: 您可能会尝试多行并选择最紧凑的解决方案。

这个想法是,如果一些圆圈已经处于或接近其最佳配置,那么这个操作很可能不会打扰它们。

我能想到的其他可能的操作:

  • 取一个离中心最远的圆(一个接触边界圆),然后随机移动到其他地方:
  • 选择一组彼此靠近的圆(例如,如果它们的中心位于随机选择的圆中)并将它们旋转一个随机角度。
  • 另一种选择(虽然有点复杂)是测量圆圈之间的面积,当它们紧密排列时:

然后,您可以选择与最大圆间区域(图像中的红色区域)相邻的一个圆,并将其与另一个圆交换,或者将其移至边界的某个位置。

(对评论的回应:)请注意,几乎可以保证这些“改进”中的每一个都会在圆圈之间产生重叠和/或不必要的空间。但在下一次迭代中,步骤 2 将移动圆圈,使它们紧密排列且不再重叠。这样,我可以有一步进行局部优化(不关心全局优化),还有一步进行全局优化(这可能会产生局部次优的解决方案)。这比一个复杂的步骤同时完成这两个步骤要容易得多。

【讨论】:

  • 线镜像是一个不错的主意,可以在不扰乱局部优化的情况下进行全局更改,但当然会产生重叠。
  • 这里有一些很棒的想法 nikie...我会尝试将它们(如果可能的话)与 rafe 的方法结合起来,看看会导致什么。
【解决方案2】:

我有一个非常幼稚的一次传球(在半径上)solution 产生了不错的结果,尽管肯定有改进的余地。在这个方向上我确实有一些想法,但我想我不妨分享一下我的想法,以防其他人也想破解它。

看起来它们在中心相交,但事实并非如此。我用嵌套循环装饰了放置函数,该循环检查每个圆与其他圆(两次)并在有交叉点时引发AssertionError

另外,我可以通过简单地对列表进行反向排序来获得接近完美的边缘,但我认为中心看起来不太好。它(几乎是唯一的事情;)在代码的 cmets 中进行了讨论。

这个想法是只查看一个圆可能存在的离散点,并使用以下生成器对其进行迭代:

def base_points(radial_res, angular_res):
    circle_angle = 2 * math.pi
    r = 0
    while 1:
        theta = 0
        while theta <= circle_angle:
            yield (r * math.cos(theta), r * math.sin(theta))
            r_ = math.sqrt(r) if r > 1 else 1
            theta += angular_res/r_
        r += radial_res

这只是从原点开始,沿着围绕它的同心圆描绘点。我们通过根据一些参数对半径进行排序来处理半径,以使大圆圈保持在中心附近(列表的开头),但在开头附近有足够的小圆圈来填充空格。然后我们迭代半径。在主循环中,我们首先遍历已经查看并保存的点。如果这些都不合适,我们开始从生成器中提取新点并(按顺序)保存它们,直到找到合适的位置。然后我们放置圆圈并浏览我们保存的点列表,拉出所有落在新圆圈内的点。然后我们重复。在下一个半径上。

我将把我的一些想法付诸实践,并使其成为mo`bettah。这可能是基于物理的想法的良好第一步,因为您可以从没有重叠的地方开始。当然,它可能已经足够紧,以至于你没有太多空间了。

另外,我从来没有玩过 numpy 或 matplotlib,所以我只写 vanilla python。里面可能有什么东西可以让它跑得更快,我得看看。

【讨论】:

  • 这看起来很棒。由于中心附近的大圆圈集中,它让我想起了埃舍尔双曲圆盘。我想如果需要的话可以改变它而不会对算法产生太大影响(我仍在努力理解你的算法,所以我可能错了)。另外,我只使用 numpy 和 matplotlib 来绘制图片。他们不会在我的存根中进行任何处理。
  • 我很好奇...你的图片中有多少个圆圈?生成这个输出需要多长时间?
  • @Rajan,图片中有 400 个圆圈。在我的机器上需要几分钟,但我的机器很慢,所以你可能不必等待太久。不幸的是,这样做对 Web 服务来说是实时的,这并没有什么好处。要运行它,只需将链接中的代码复制并粘贴到您提供的存根函数上即可。
  • @Rajan,整个代码可在我帖子第一段的链接中找到。它位于pastebin.com/VciE4Js7。它大约有 124 行,所以我不想把它放在这里。请注意,它还没有接近完全调整,我仍处于“使其工作”阶段;)它应该作为导出 positionCircles 函数的独立模块工作,或者只是粘贴在您提供的存根函数上。
  • @aaronasterling:这太美了!非常感谢!这里还有一个问题需要你思考:你将如何设置大圆的半径并用尽可能多的不重叠的圆来填充它?含义:以大圆的直径为主要参数,而不是选择小圆的数量?我一直在尝试调整您的代码来执行此操作,但是由于大圆的半径从未起作用,所以我遇到了麻烦。
【解决方案3】:

您能否将圆圈视为带电腔中的带电粒子并寻找稳定的解决方案?也就是说,圆圈根据接近程度相互排斥,但被吸引到原点。几个模拟步骤可能会给你一个不错的答案。

【讨论】:

  • 这对我来说听起来很有希望,尤其是在加上某种退火计划 (en.wikipedia.org/wiki/Simulated_annealing) 时,它会在模拟早期引入随机噪声来混合粒子,并且(继续电荷类比)收紧在模拟后期将电位提高到近一个方形井,以更准确地实施无穿透和全局圆形约束。
  • 这个答案是一种“能量最小化”,可以用作 nikie 答案的第 2 步。
  • @Chris Johnson:没错。我想象每个圆圈都会被吸引到中心,两个圆圈一旦开始重叠就会相互排斥。但我想结果或多或少是一样的。
【解决方案4】:

听起来像是圆形包装问题,这里有一些信息:

【讨论】:

  • google搜索结果中常说的圈装算法不适用于这个问题。该算法从平面图开始并得出其相应的“圆形包装”。这是瑟斯顿的“圆包装定理”的应用。但是,我们没有平面图可以开始。如果我们有平面图,那么找到圆包装将是一个多项式时间算法。在其一般形式中,这个问题是 NP 完全的。
  • 另一个“圆包装算法”的问题陈述是这样的:给定一个复杂的 K(图 K)和适当的边界条件,计算 K 的相应圆包装的半径......它基本上从一个图表开始,告诉哪些圆相互接触(图的顶点表示圆,边缘表示圆之间的接触)。但是顶点和边是任意放置在空间中的。必须找到圆的半径和位置,以满足图形表示的接触关系。和我们的问题不一样。
【解决方案5】:

http://en.wikipedia.org/wiki/Apollonian_gasket

这似乎与您正在尝试做的事情有些相关,并且可能会为您提供一些潜在的限制。

【讨论】:

  • 我不知道如何使用垫圈,但它们很漂亮。感谢您发布它。
  • 我认为这可以在特定的输入情况下工作:从三个最大的曲率/半径输入生成一个 Apollonian 垫片,然后记下随后生成的曲率/半径。如果提供的其余半径对应的半径等于或小于生成的半径,那么您可以简单地绘制同心圆,您希望在垫圈中找到生成的圆。然而,这不是一个很好的解决方案,但它可能会提供一些额外的见解。
猜你喜欢
  • 2021-06-01
  • 2019-07-23
  • 2014-09-22
  • 2011-01-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-19
相关资源
最近更新 更多