简体   繁体   中英

How to efficiently cover a set of points with circles when you can't access point coordinates?

Suppose I have a finite set of points distributed in a unit square. I can't access the point coordinates; instead, I can only specify a (point, radius) pair and see how many points fall inside that circle. I want to find a set of circles such that each point is in at least one circle, and no circle contains more than 1000 points. What's an efficient way to do this? Eg a way that minimizes the expected number of (point, radius) searches?

I tried a recursive approach. Eg f(point, radius) takes a circle and returns a set of smaller circles that cover it. Then recurse until each circle contains fewer than 1000 points. But there's not a straightforward (to me) way to choose the smaller circles in the recursive step.

Edit: Circles are allowed to overlap with each other / with the outside of the square.

Not having a strict partition ("strict" - where the circles in the solution may not overlap and points must appear in exactly 1 circle) simplifies the problem.

The straight-forward way to subdivide a circle under those circumstances is to form a set of child circles that circumscribe the four quadrants of the parent...

在此处输入图像描述

Here's a (cursorily tested) demo using that approach

 class Circle { constructor(x,y,radius) { Object.assign(this, { x, y, radius }) this.rSquared = radius*radius } contains(point) { let dx = point.x - this.x let dy = point.y - this.y return dx*dx + dy*dy < this.rSquared } draw() { ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI); ctx.stroke(); } subdivide() { const halfR = this.radius / 2.0 const smallR = this.radius * Math.SQRT2 / 2.0 return [ new Circle(this.x-halfR, this.y-halfR, smallR), new Circle(this.x-halfR, this.y+halfR, smallR), new Circle(this.x+halfR, this.y-halfR, smallR), new Circle(this.x+halfR, this.y+halfR, smallR), ] } } // this class keeps a set of random points and answers countInCircle() // solutions may only call countInCircle() class Puzzler { constructor(count) { this.points = [] for (let i=0; i<count; i++) { let point = { x: Math.random()*width, y: Math.random()*height} this.points.push(point) } } // answer how many points fall inside circle countInCircle(circle) { return this.points.reduce((total, p) => total += circle.contains(p)? 1: 0, 0); } drawSolution(circles) { // draw the random points this.points.map(p => ctx.fillRect(px,py,2,2)) // draw the circles in the solution ctx.strokeStyle = 'lightgray' circles.forEach(circle => circle.draw()) // log some stats - commented a few of these out for snippet brevity const counts = circles.map(circle => this.countInCircle(circle)); console.log('circles:', circles.length) // console.log('counts:', counts.join(', ')) // console.log('counts above 100:', counts.filter(c => c > 100).length) const averageCount = counts.reduce((a, b) => a + b) / counts.length console.log('average count:', averageCount.toFixed(2)) const uncovered = this.points.reduce((total, point) => { return total + (circles.some(circle => circle.contains(point))? 0: 1) }, 0) console.log('uncovered points:', uncovered) } } // setup canvas const canvas = document.getElementById('canvas') const { width, height } = canvas const ctx = canvas.getContext('2d') // setup puzzle const count = 1000 const maxCountPer = 100 const puzzler = new Puzzler(count, maxCountPer) // begin with an encompasing circle, subdivide and solve recursively // until all subdivided circles meet the count criterion let r = width*Math.SQRT2/2 let c = new Circle(width/2.0, width/2.0, r) let solution = solve(c); function solve(circle) { let result = [] let count = puzzler.countInCircle(circle) if (count > 0 && count <= maxCountPer) { result.push(circle); } else if (count > maxCountPer) { circle.subdivide().forEach(c => { result.push(...solve(c)) }) } return result } requestAnimationFrame(() => { ctx.clearRect(0, 0, canvas.width, canvas.height); puzzler.drawSolution(solution) });
 <h1>Circles Puzzle</h1> <canvas style="border: 1px solid gray;" id="canvas" height="800" width="800"></canvas>

Assumption: When you pick a point and a radius, you get back a list of points that are in the containing circle. Ie, you know which points are covered by which circles.

If that's correct,then you can map out the approximate relative location of all points, after which answers to this similar question should carry you over the finish line.

To map out the relative location of all points:

Note that you can find the distance between any pair of points by centering your circle on one and using binary search on your radius to find the distance to the other within whatever precision you want to use.

Next choose three arbitrary points that aren't too close together. Pick an arbitrary point. Grow the radius, say to 1/4. Pick an arbitrary point close to that radius (by incrementing radius a bit to get another point, or using binary search on radius). Say the distance between these first two points is d. Pick a third point at distance >= d from the first two points but ideally close to d, again by incrementing the two radii or binary search on the same.

Now you have a roughly equilateral triangle. It isn't important that it's equilateral, but it is important that the points aren't very close, and aren't co-linear.

Next, give these three points coordinates. Say the first point is at (0,0), the second point is at (0, dist to first point). The third point will have two possible locations based on its distance from the first two. Choose the one in the first quadrant (arbitrarily).

All other points can now be positioned relative to this triangle by finding their distance two the points of the triangle.

For purposes of your problem, it doesn't matter that the cloud of points is rotated relative to the input, or that we don't know where the unit square is relative to the points. You have a cloud of points with (approximately) known coordinates, and can proceed accordingly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM