半径为r的圆的最小数量,以覆盖n个点

时间:2013-04-08 14:47:42

标签: algorithm computational-geometry

覆盖所有n点所需的半径为r的最小圆数是多少? r和n将作为输入给出,接着是n对整数,表示n个点的x-y坐标。 r是实数并且大于0.n是< 20。

如果该点位于圆内,则圆圈覆盖一个点。 如果点与圆心之间的距离小于或等于r,则点位于圆内。

9 个答案:

答案 0 :(得分:11)

这可能不是最佳解决方案,而是尝试对其进行优化。

算法基于随机抽样:

  1. 在地图上生成N个圈子
  2. 删除所有未覆盖任何一点的圈子
  3. 按圈数下降的圆圈排序
  4. Foreach圆圈(已排序) - 标记由圆圈覆盖的点。如果圆圈未覆盖任何新点,请从列表中删除。
  5. 以下是您可以实时预览的代码:http://jsfiddle.net/rpr8qq4t/示例结果(每30分13个圈子): 13 circles per 30 points

    参数化方式:

      var POINTS_NUMBER = 30;
      var RADIUS = 50;
      var SAMPLE_COUNT = 400;
    

    可能会添加一些优化(例如,某些圈子可能过早地从列表中排除)

    修改

    1. 步骤1中的更改​​带来更好的结果:为每个点生成N个圆圈(至少覆盖点的圆圈) 新版本:http://jsfiddle.net/nwvao72r/3/
    2. 编辑2(最终算法)

      最后:

      1. Foreach点产生N = 10个随机距离小于R点的圆(圆的半径,所以我们确定每个圆至少有一个点属于它,每个点属于至少一个圆)
      2. 重复直到覆盖所有点:
        • 获取圈子覆盖最大未覆盖点数。将分数标记为已覆盖。
      3. 以下是为我带来最佳效果的版本,您可以在此处查看 http://jsfiddle.net/nwvao72r/4/ 这里每30分钟平均有12个圈子。

        enter image description here

答案 1 :(得分:8)

我确定这个问题是NP难的,虽然我不打算在这里证明这一点。

如果它是NP难的,那么为了找到保证最优的解决方案,我推荐以下方法:

  1. 查找所有“好”潜在圈子展示位置,以及每个记录中包含哪些积分。
  2. 使用这些点解决集合覆盖问题。 (这个问题是NP难的。)
  3. 良好的圈子展示位置

    如果任何2个点之间的距离小于2r,那么正好有两个半径为r的圆圈可以通过这些点:

    2 circles through 2 points

    [编辑:我对“最好的”圈子的原始描述是错误的,虽然这不会导致问题 - 感谢评论者乔治描述正确的思考方式。]

    如果一个圆圈覆盖了一组最大点(意味着圆圈无法重新定位以覆盖相同的一组点加上至少1个点),那么该圆圈可以滑动直到其边界恰好触及两个点它覆盖 - 比如说,向左滑动直到它接触已经覆盖的点,然后顺时针绕着这个触摸点旋转它,直到它接触另一个已经覆盖的点。这个移动的圆圈将完全覆盖原始圆圈所覆盖的一组点。此外,我们永远不需要考虑覆盖非最大点集的圆,因为覆盖这些点和更多点的最大圆至少同样有用并且不再花费更多。这意味着我们只需要考虑触及两点的圆圈。如果我们为输入中每个足够接近的点生成两个圆圈,我们将生成我们可能需要的所有圆圈。

    因此,我们的潜在圆圈池每对点最多包含2个圆圈,总共最多有n *(n-1)个潜在圆圈。 (通常会有更少的,因为一些点通常会超过2r,因此不能被半径为r的单个圆覆盖。)此外,我们还需要一个额外的圆,每个点超过2r < em>任何其他点 - 这些圈子也可能以这些远程点为中心。

    设置封面

    我们真正关心的是每个潜在圈子所涵盖的一组点数。因此,对于每个潜在的圈子,找到它所涵盖的点。这可以在整个O(n ^ 3)时间内完成,对每个潜在的圆使用O(n)遍。为了加快速度,如果我们发现两个不同的圆圈覆盖完全相同的点集,我们只需要保留其中一个圆圈(覆盖点集)。此外,我们可以丢弃任何覆盖点集,这是一些其他覆盖点集的子集 - 在这种情况下,最好选择较大的覆盖点集。

    最后,我们有一组覆盖点集,我们希望找到涵盖每个点的这些集的最小子集。这是set cover problem。我不知道要解决这个问题的具体算法,但branch and bound是解决此类问题的标准方法 - 它通常比简单详尽的回溯搜索快得多。我首先通过首先找到一个(或多个)启发式解决方案来进行搜索,希望产生一个良好的上限,这将减少分支和边界搜索时间。我认为即使最好的算法在最坏的情况下也需要指数时间,尽管我认为n&lt; 20,因为最多19 * 18 = 342个不同的点。

答案 2 :(得分:4)

我意识到圆圈不必以点为中心,因此计算通过两个点的任意组合的所有圆,包括以每个点为中心的圆。 然后我找到每个圆圈覆盖的点并使用贪婪算法找到覆盖所有点的最小圆圈集,但同样,它可能不是 最小圆圈集,但是相当容易计算

from collections import namedtuple
from itertools import product
from math import sqrt
from pprint import pprint as pp

Pt = namedtuple('Pt', 'x, y')
Cir = namedtuple('Cir', 'x, y, r')

def circles_from_p1p2r(p1, p2, r):
    'Following explanation at http://mathforum.org/library/drmath/view/53027.html'
    (x1, y1), (x2, y2) = p1, p2
    if p1 == p2:
        #raise ValueError('coincident points gives infinite number of Circles')
        return None, None
    # delta x, delta y between points
    dx, dy = x2 - x1, y2 - y1
    # dist between points
    q = sqrt(dx**2 + dy**2)
    if q > 2.0*r:
        #raise ValueError('separation of points > diameter')
        return None, None
    # halfway point
    x3, y3 = (x1+x2)/2, (y1+y2)/2
    # distance along the mirror line
    d = sqrt(r**2-(q/2)**2)
    # One answer
    c1 = Cir(x = x3 - d*dy/q,
             y = y3 + d*dx/q,
             r = abs(r))
    # The other answer
    c2 = Cir(x = x3 + d*dy/q,
             y = y3 - d*dx/q,
             r = abs(r))
    return c1, c2

def covers(c, pt):
    return (c.x - pt.x)**2 + (c.y - pt.y)**2 <= c.r**2

if __name__ == '__main__':
    for r, points in [(3, [Pt(*i) for i in [(1, 3), (0, 2), (4, 5), (2, 4), (0, 3)]]),
                      (2, [Pt(*i) for i in [(1, 3), (0, 2), (4, 5), (2, 4), (0, 3)]]),
                      (3, [Pt(*i) for i in [(-5, 5), (-4, 4), (3, 2), (1, -1), (-3, 2), (4, -2), (6, -6)]])]:
        n, p = len(points), points  
        # All circles between two points (which can both be the same point)
        circles = set(sum([[c1, c2]
                           for c1, c2 in [circles_from_p1p2r(p1, p2, r) for p1, p2 in product(p, p)]
                           if c1 is not None], []))
        # points covered by each circle 
        coverage = {c: {pt for pt in points if covers(c, pt)}
                    for c in circles}
        # Ignore all but one of circles covering points covered in whole by other circles
        #print('\nwas considering %i circles' % len(coverage))
        items = sorted(coverage.items(), key=lambda keyval:len(keyval[1]))
        for i, (ci, coveri) in enumerate(items):
            for j in range(i+1, len(items)):
                cj, coverj = items[j]
                if not coverj - coveri:
                    coverage[cj] = {}
        coverage = {key: val for key, val in coverage.items() if val}
        #print('Reduced to %i circles for consideration' % len(coverage))

        # Greedy coverage choice
        chosen, covered = [], set()
        while len(covered) < n:
            _, nxt_circle, nxt_cov = max((len(pts - covered), c, pts)
                                         for c, pts in coverage.items())
            delta = nxt_cov - covered
            covered |= nxt_cov
            chosen.append([nxt_circle, delta])

        # Output
        print('\n%i points' % n)
        pp(points)
        print('A minimum of circles of radius %g to cover the points (And the extra points they covered)' % r)
        pp(chosen)

显示三次运行的输出是:

5 points
[Pt(x=1, y=3), Pt(x=0, y=2), Pt(x=4, y=5), Pt(x=2, y=4), Pt(x=0, y=3)]
A minimum of circles of radius 3 to cover the points (And the extra points they covered)
[[Cir(x=2.958039891549808, y=2.5, r=3),
  {Pt(x=4, y=5), Pt(x=0, y=3), Pt(x=1, y=3), Pt(x=0, y=2), Pt(x=2, y=4)}]]

5 points
[Pt(x=1, y=3), Pt(x=0, y=2), Pt(x=4, y=5), Pt(x=2, y=4), Pt(x=0, y=3)]
A minimum of circles of radius 2 to cover the points (And the extra points they covered)
[[Cir(x=1.9364916731037085, y=2.5, r=2),
  {Pt(x=0, y=3), Pt(x=1, y=3), Pt(x=0, y=2), Pt(x=2, y=4)}],
 [Cir(x=4, y=5, r=2), {Pt(x=4, y=5)}]]

7 points
[Pt(x=-5, y=5),
 Pt(x=-4, y=4),
 Pt(x=3, y=2),
 Pt(x=1, y=-1),
 Pt(x=-3, y=2),
 Pt(x=4, y=-2),
 Pt(x=6, y=-6)]
A minimum of circles of radius 3 to cover the points (And the extra points they covered)
[[Cir(x=3.9951865152835286, y=-0.8301243435223524, r=3),
  {Pt(x=3, y=2), Pt(x=1, y=-1), Pt(x=4, y=-2)}],
 [Cir(x=-2.0048134847164714, y=4.830124343522352, r=3),
  {Pt(x=-4, y=4), Pt(x=-3, y=2), Pt(x=-5, y=5)}],
 [Cir(x=6.7888543819998315, y=-3.1055728090000843, r=3), {Pt(x=6, y=-6)}]]

答案 3 :(得分:3)

平铺然后摇晃

  1. TILE:找到包围所有点的矩形
  2. 将矩形区域平铺,间隔r * sqrt(2)。
  3. 对于每个点,计算它们是哪个圆以及每个圆中的点。
  4. 删除任何没有积分的圈子。
  5. 删除任何仅包含多个圆圈中包含的点的圆圈。
  6. 重复5,直到没有更多。
  7. Jiggle:对于每个圈子:尝试移动它以查看它是否可以覆盖其原始点数加上最多的新点数。
  8. 再做4和5。
  9. 重复7,直到摇晃不会改变时间耗尽的圆圈点。
  10. 步骤2,如果平铺非常稀疏,可以通过踩过每个点并仅计算/保留那些包含点的圆来优化平铺。

答案 4 :(得分:3)

来自Gautam K. Das等人的论文“关于离散单元磁盘盖问题”。人:

  

最小几何磁盘覆盖。在最小几何磁盘覆盖问题中,输入由平面中的一组点组成,问题是找到一组最小基数的单位磁盘,其联合覆盖了这些点。与DUDC不同,磁盘中心不限于从给定的离散集中选择,而是可以在平面中的任意点处居中。同样,这个问题是NP-hard [9]并且有一个PTAS解决方案[11,12]。

参考文献:

  
      
  1. R上。 Fowler,M。Paterson和S. Tanimoto,飞机上的最佳包装和覆盖是NP-complete,Information Processing Letters,vol 12,pp.133-137,1981。
  2.   
  3. -G。 Frederickson,平面图中最短路径的快速算法,应用,SIAM J. on Computing,vol 16,pp.1004-1022,1987。
  4.   
  5. 吨。 Gonzalez,涵盖多维空间中的一组点,信息处理快报,第40卷,第181-188页,1991年。
  6.   
  7. d。 Hochbaum和W. Maass,“图像处理和VLSI中覆盖和包装问题的近似方案”,J。ACM,第32卷,第130-136页,1985年。
  8.   

答案 5 :(得分:1)

这是我的第一个答案,我将在另一个答案中提到它。 但请参阅我之后的回答,即在两点之间考虑圈子而非此。 这是一个用Python编写的贪婪算法,它会找到 a minima,但我不知道它是否是 最小解决方案。

dbg = False
if not dbg:
    r, n = (int(s) for s in input('r n: ').split())
    points = p = [ tuple(int(s) for s in input('x%i y%i: ' % (i, i)).split())
                   for i in range(n) ]
else:
    r, n, points = 3, 5, [(1, 3), (0, 2), (4, 5), (2, 4), (0, 3)]; p = points

# What a circle at each point can cover
coverage = { i: frozenset(j
                          for j in range(i, n)
                          if (p[i][0] - p[j][0])**2 + (p[i][1] - p[j][1])**2 <= r**2)
             for i in range(n)}

# Greedy coverage choice
chosen, covered = [], set()
while len(covered) < n:
    # Choose the circle at the point that can cover the most ADDITIONAL points.
    _, nxt_point, nxt_cov = max((len(pts - covered), i, pts)
                                for i, pts in coverage.items())
    covered |= nxt_cov
    chosen.append(nxt_point)
print('Cover these points:\n  %s' % '\n  '.join('%s, %s' % p[i] for i in chosen))

这是一个示例运行:

r n: 3 5
x0 y0: 1 3
x1 y1: 0 2
x2 y2: 4 5
x3 y3: 2 4
x4 y4: 0 3
Cover these points:
  1, 3
  4, 5

注意:数据i / o很简陋,但算法应该清晰

答案 6 :(得分:1)

如果中心C(cx, cy)的圆圈覆盖P(px, py)点,则距离|CP| < rr - 半径)。 因此,圆的中心可以覆盖点P的区域是圆心,其中心位于P和半径r。现在让我们在给定点和半径r中绘制具有中心的所有圆。如果某些圆相交,那么我们可以在覆盖相应点的交叉点绘制具有中心的新圆。 因此,对于每对输入点,我们检查圆是否相交。

假设输入点是顶点,并且交点在它们之间获得边缘。 现在我们有一个已知的图形问题最小边缘覆盖http://en.wikipedia.org/wiki/Edge_cover可以在多项式时间内解决(虽然限制n < 20暴力可能是可以接受的)

更新。那不是边缘封面。我的错误。

答案 7 :(得分:1)

我不确定这是否正确,但如果我们不需要解决方案圈的确切位置,在我看来,我们可以通过查看点群来解决这个问题:在任何一个在解决方案中,任意两点之间的距离应小于或等于2 * r。

算法:

1. j_random_hacker indicated that any solution-circle could be shifted so that
   two of its covered-points lay on its circumference without changing the 
   original covered-points. Since the solution-circle radius is given, for each 
   point: (a) calculate potential circle-centers using the point, radius, and 
   each other point that is at a distance of 2*r or less, (b) for each circle, 
   list the cluster of points that it could cover. Sort each cluster and, for
   each point, remove duplicate clusters. 

2. For each cluster group in 1., choose the cluster that has the greatest point-
   count, that is, the cluster that is most shared.

3. Remove duplicates and clusters that are sub-sequences of other clusters 
   from 2., and present the resulting size of 2. (perhaps together with the 
   chosen clusters) as the solution.


等边三角形的输出,r = 3,[(0,0),(5.196152422706632,3),(5.196152422706632,-3)]

*Main> solve
(2,[[(0.0,0.0),(5.196152422706632,3.0)],[(0.0,0.0),(5.196152422706632,-3.0)]])


输出Paddy3118的例子,r = 3,[(1,3),(0,2),(4,5),(2,4),(0,3)]:

*Main> solve
(1,[[(0.0,2.0),(0.0,3.0),(1.0,3.0),(2.0,4.0),(4.0,5.0)]])


输出r = 3,[( - 5,5),( - 4,4),(3,2),(1,-1),( - 3,2),(4 ,-2),(6,-6)]:

*Main> solve
(3,[[(-5.0,5.0),(-4.0,4.0),(-3.0,2.0)],[(1.0,-1.0),(3.0,2.0),(4.0,-2.0)],
    [(4.0,-2.0),(6.0,-6.0)]])


Haskell代码:

import Data.List (delete, nub, nubBy, isInfixOf, sort, sortBy, maximumBy)

points = [(0,0),(5.196152422706632,3),(5.196152422706632,-3)]--[(1,3),(0,2),(4,5),(2,4),(0,3)]--[(-5,5),(-4,4),(3,2),(1,-1),(-3,2),(4,-2),(6,-6)]
r = 3
twoR = 2*r

circleCenters (x1,y1) (x2,y2) =
  let q = sqrt $ (x2-x1)^2 + (y2-y1)^2
      (x3, y3) = ((x1+x2)/2,(y1+y2)/2)
      first = (x3 + sqrt(r^2-(q/2)^2)*(y1-y2)/q, y3 + sqrt(r^2-(q/2)^2)*(x2-x1)/q)
      second = (x3 - sqrt(r^2-(q/2)^2)*(y1-y2)/q, y3 - sqrt(r^2-(q/2)^2)*(x2-x1)/q)
  in [first,second]

isInCircle (center_x,center_y) (x,y) = (x-center_x)^2 + (y - center_y)^2 <= r^2

findClusters (px,py) = 
  nub [sort $ [(px,py)] ++ filter (isInCircle a) potentialPoints | a <- potentialCircleCenters]
    where
      potentialPoints = filter (\(x,y) -> (x-px)^2 + (y-py)^2 <= twoR^2) (delete (px,py) points)
      potentialCircleCenters = concatMap (circleCenters (px,py)) potentialPoints

solve = (length bestClusters, bestClusters) where
  clusters = map findClusters points
  uniqueClusters = nub . concat $ clusters
  bestClusterForEachPoint = map (maximumBy (\a b -> compare (length a) (length b))) clusters
  bestClusters = nub . nubBy (\a b -> isInfixOf a b) . sortBy (\a b -> compare (length b) (length a)) 
                 $ bestClusterForEachPoint

答案 8 :(得分:0)

如果您将n个圆圈(半径为r)全部放在每个点的中心位置,则查找最大重叠区域/点并将新圆圈(半径为r)置于中心位置那个地区。我不确定这是否是解决方案的最佳方式(如果这是一种解决问题的方法,除了蛮力方式),我相信你可以用相当多的数学实现它,并且从而降低解决方案的运行时复杂性。希望这可以帮助。请提供反馈。