如果一个点在圆内,则该圆覆盖了该点。 如果点与圆心之间的距离小于或等于r,则该点位于圆内。
这可能不是最佳解决方案,但它是对其进行优化的尝试。
算法基于随机取样:
这里有一些代码,您可以预览它:http://jsfiddle.net/rpr8qq4t/ 示例结果(30个点的13个圆):
参数化:
var POINTS_NUMBER = 30;
var RADIUS = 50;
var SAMPLE_COUNT = 400;
它可以添加一些优化(例如,有些圆可能太早从列表中排除)
编辑:
编辑2(最终算法)
最终版本:
这是最适合我的版本,您可以在此处查看:http://jsfiddle.net/nwvao72r/4/,平均每30个点使用12个圆。
[编辑:我的“最佳可能”圆的原始描述是错误的,尽管这不会导致问题 - 感谢评论者乔治描述了正确的思考方式。]
如果一个圆覆盖了一组最大点(意思是该圆不能重新定位以覆盖相同的点集加上至少1个),那么该圆可以滑动直到其边界恰好接触到它覆盖的两个点中的任意两个 - 比如说,通过将其向左滑动直到它接触到已经覆盖的点,然后顺时针围绕这个接触点旋转直到它接触到另一个已经覆盖的点。这个移动的圆将完全覆盖原始圆覆盖的点集。此外,我们永远不需要考虑覆盖非最大点集的圆,因为覆盖这些点和更多点的最大圆至少同样有用而且成本不高。这意味着我们只需要考虑接触两个点的圆。只要为输入中每对足够接近的点生成两个圆,我们就已经生成了所有可能需要的圆。
我们的潜在圆池最多包含每对点之间的2个圆,总共最多可能有n*(n-1)个潜在圆。(通常会更少,因为某些点对通常会相距超过2r,因此无法被半径为r的单个圆覆盖。)此外,我们还需要一个额外的圆,用于每个距离任何其他点都超过2r的点——这些圆可以放置在那些远离的点上。最后,我们有一组覆盖点集,并且我们想要找到覆盖每个点的最小子集。这就是集合覆盖问题。我不知道解决这个问题的具体算法,但分支定界法是解决这类问题的标准方法——它通常比简单的穷举回溯搜索快得多。我首先会通过先找到一个(或多个)启发式解来为搜索做好准备,希望能产生一个良好的上界,以减少分支定界搜索时间。我认为即使是这个问题的最佳算法在最坏情况下也需要指数时间,但对于n<20,我认为这将是可管理的,因为最多有19*18=342个不同的点集。
来自Gautam K. Das等人的论文"On the Discrete Unit Disk Cover Problem":
最小几何圆盖问题。在最小几何圆盖问题中,输入由平面上的一组点组成,问题是找到一组单位圆的最小基数,使其并集覆盖这些点。与DUDC不同,圆心不受限于从给定的离散集合中选择,而可以位于平面上的任意点上。同样,这个问题是NP-hard [9],但有PTAS解决方案[11, 12]。
参考文献:
- R. Fowler, M. Paterson和S. Tanimoto,“Optimal packing and covering in the plane are NP-complete”,Information Processing Letters,vol 12,pp. 133-137,1981年。
- G. Frederickson,“Fast algorithms for shortest paths in planar graphs, with applications”,SIAM J. on Computing,vol 16,pp. 1004-1022,1987年。
- T. Gonzalez,“Covering a set of points in multidimensional space”,Information Processing Letters,vol 40,pp. 181-188,1991年。
- D. Hochbaum和W. Maass,“Approximation schemes for covering and packing problems in image processing and VLSI”,J. ACM,vol 32,pp. 130-136,1985年。
平铺然后晃动
步骤2中,可以通过遍历每个点并计算/仅保留那些如果平铺非常稀疏,则会包含点的圆来优化平铺。
我意识到圆不一定要在点上居中,因此计算通过任何两个点的所有圆,包括以每个点为中心的圆。 然后找出每个圆覆盖的点,并使用贪心算法找到覆盖所有点的最小圆集,但是这可能不是最小圆集,但计算起来相对容易。
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)}]]
我不确定这是否正确,但如果我们不需要解的圆的确切位置,那么看起来我们可以通过查看点簇来解决这个问题:在任何一个解的圆中,任意两点之间的距离应小于或等于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
这是我的第一个答案,我会保留它,因为另一个答案引用了它。 但请看我的后续答案,该答案考虑了两点之间的圆而不是这种方法。 以下是用Python编写的贪心算法,可以找到a的最小值,但我不知道它是否是the最小解。
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
C(cx,cy)
的圆覆盖点P(px,py)
,则距离|CP|<r
(r
-半径)。因此,覆盖点P
的圆心可能在圆心为P
且半径为r
的圆内。现在让我们画出所有以给定点为圆心且半径为r
的圆。如果一些圆相交,则我们可以画一个新的圆,其圆心位于这种相交处,并覆盖相应的点。因此,对于每对输入点,我们检查圆是否相交。n<20
,暴力方法可能是可接受的)r
的n
个圆,找到最大重叠的区域/点并在该区域中心放置新的半径为r
的圆。我不确定这是否是解决方案的最佳方式(除了暴力方法之外),但我相信您可以用相当多的数学实现它,从而降低解决方案的运行时间复杂度。希望这有所帮助。请给予反馈。