覆盖所有n点所需的半径为r的最小圆数是多少? r和n将作为输入给出,接着是n对整数,表示n个点的x-y坐标。 r是实数并且大于0.n是< 20。
如果该点位于圆内,则圆圈覆盖一个点。 如果点与圆心之间的距离小于或等于r,则点位于圆内。
答案 0 :(得分:11)
这可能不是最佳解决方案,而是尝试对其进行优化。
算法基于随机抽样:
以下是您可以实时预览的代码: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 :(得分:8)
我确定这个问题是NP难的,虽然我不打算在这里证明这一点。
如果它是NP难的,那么为了找到保证最优的解决方案,我推荐以下方法:
如果任何2个点之间的距离小于2r,那么正好有两个半径为r的圆圈可以通过这些点:
[编辑:我对“最好的”圈子的原始描述是错误的,虽然这不会导致问题 - 感谢评论者乔治描述正确的思考方式。]
如果一个圆圈覆盖了一组最大点(意味着圆圈无法重新定位以覆盖相同的一组点加上至少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)
平铺然后摇晃
步骤2,如果平铺非常稀疏,可以通过踩过每个点并仅计算/保留那些包含点的圆来优化平铺。
答案 4 :(得分:3)
来自Gautam K. Das等人的论文“关于离散单元磁盘盖问题”。人:
最小几何磁盘覆盖。在最小几何磁盘覆盖问题中,输入由平面中的一组点组成,问题是找到一组最小基数的单位磁盘,其联合覆盖了这些点。与DUDC不同,磁盘中心不限于从给定的离散集中选择,而是可以在平面中的任意点处居中。同样,这个问题是NP-hard [9]并且有一个PTAS解决方案[11,12]。
参考文献:
- R上。 Fowler,M。Paterson和S. Tanimoto,飞机上的最佳包装和覆盖是NP-complete,Information Processing Letters,vol 12,pp.133-137,1981。
- -G。 Frederickson,平面图中最短路径的快速算法,应用,SIAM J. on Computing,vol 16,pp.1004-1022,1987。
- 吨。 Gonzalez,涵盖多维空间中的一组点,信息处理快报,第40卷,第181-188页,1991年。
- d。 Hochbaum和W. Maass,“图像处理和VLSI中覆盖和包装问题的近似方案”,J。ACM,第32卷,第130-136页,1985年。
醇>
答案 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| < r
(r
- 半径)。
因此,圆的中心可以覆盖点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
)置于中心位置那个地区。我不确定这是否是解决方案的最佳方式(如果这是一种解决问题的方法,除了蛮力方式),我相信你可以用相当多的数学实现它,并且从而降低解决方案的运行时复杂性。希望这可以帮助。请提供反馈。