将不同半径的N个圆圈放置在较大的圆圈内而不重叠

时间:2010-10-03 21:27:35

标签: algorithm language-agnostic geometry

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

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

  1. 我要求“小”,因为“最小”半径将其转换为更难的问题(最低版本已被证明是NP难/完全 - 请参阅问题末尾附近的脚注)。我们不需要最低限度。如果圆圈形状看起来相当圆,那就足够了。
  2. 您可以假设Rmax / Rmin < 20如果它有帮助。
  3. 低优先级问题 - 该计划应该能够处理2000多个圈子。首先,即使100-200圈也应该没问题。
  4. 您可能已经猜到圆圈不需要紧紧地包在一起,甚至不能互相接触。
  5. 目的是提出一个视觉上令人愉悦的给定圆形排列,它可以放在一个更大的圆圈内,而不会留下太多的空白空间。 (就像color blindness test picture中的圆圈一样)。 alt text

    你可以使用下面的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)
    

    已添加信息:Google搜索结果中通常引用的圈子打包算法不适用于此问题。

    另一个“圆形填充算法”的问题陈述是:给定复数K(在此上下文中的图形称为单纯复形,或简称复杂)和适当的边界条件,计算相应圆形填充的半径ķ....

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

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

    圆形填充定理 - 每个圆形填充都有一个相应的平面图(这是简单/明显的部分),每个平面图都有一个相应的圆形填充(不那么明显的部分)。图表和包装是彼此的对偶,是独一无二的。

    从我们的问题开始,我们没有平面图或切线关系。

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

6 个答案:

答案 0 :(得分:18)

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

我认为类似的东西在这里是可能的:

  1. 从随机中心位置开始
  2. “优化”这些位置,因此没有重叠的圆圈,因此圆圈尽可能接近,通过增加重叠圆圈之间的距离并减小其他圆圈之间的距离,直到它们紧密包装。这可以通过某种能量最小化来完成,或者可能存在更有效的贪婪解决方案。alt text
  3. 将迭代改进运算符应用于中心位置
  4. 转到2,在最大迭代次数之后中断或者如果最后一次迭代没有找到任何改进
  5. 有趣的问题是:您可以在步骤3中使用哪种“迭代改进运算符”?我们可以假设该阶段的位置局部最优,但可以通过重新排列大部分圆来改进它们。我的建议是通过圈子任意选择一条线。然后取出线条的所有圆圈“左”并在垂直于该线的某个轴上镜像它们: alt text 您可能会尝试多行并选择一个可以获得最紧凑解决方案的行。

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

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

    • 从中心距离最远的一个圆圈(一个接触边界圆圈),随机移动到其他地方: alt text
    • 选择一组彼此靠近的cirlces(例如,如果它们的中心位于随机选择的圆圈中)并以随机角度旋转它们。
    • 另一种选择(虽然有点复杂)是测量圆圈之间的区域,当它们紧密包装时:

    alt text

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

    (对评论的回应:)请注意,这些“改进”中的每一个几乎都可以保证在圆圈之间创建重叠和/或不必要的空间。但是在下一次迭代中,第2步将移动圆圈,使它们紧密堆积并且不再重叠。这样,我可以为本地优化做一步(不关心全局优化),还有一步用于全局优化(可能会创建本地次优解决方案)。这比两个复杂的步骤要容易得多。

答案 1 :(得分:15)

我有一个非常幼稚的一次通过(在半径上)solution可以产生不错的结果,虽然肯定有改进的余地。我确实在这个方向上有一些想法,但我想也可以分享我所拥有的,以防任何其他人想要破解它。

alt text

看起来它们在中心相交,但它们没有。我使用嵌套循环修饰了贴装函数,该循环检查每个圆圈与其他每个圆圈(两次),如果有交叉点,则会引发AssertionError

此外,我可以通过简单地对列表进行反向排序来使边缘接近完美,但我不认为中心看起来那么好。它是(几乎唯一的东西;)在对代码的评论中讨论过。

我们的想法是只查看圆圈可能存在的离散点并使用以下生成器迭代它们:

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所以我只写了香草蟒蛇。可能会有一些东西让它运行得更快,我不得不看。

答案 2 :(得分:7)

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

答案 3 :(得分:2)

听起来像Circle Packing问题,这里有一些信息:

答案 4 :(得分:1)

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

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

答案 5 :(得分:-1)

你可以尝试使用2d物理库,只需将你的2d圆圈倒入一个更大的圆形容器中 - 等待它们安置到位。