【问题标题】:Circle Separation Distance - Nearest Neighbor Problem圆分离距离 - 最近邻问题
【发布时间】:2011-06-18 08:24:40
【问题描述】:

我有一组在二维平面上具有给定位置和半径的圆。我想确定每个圆是否与任何其他圆相交以及将两者分开所需的距离。在我目前的实现中,我只是遍历所有可能的圆圈组合,然后进行计算。不幸的是,这个算法是 O(n^2),很慢。

这些圆通常会成组聚集,它们的半径相似(但不同)。圈数的近似最大值约为 200。算法不必精确,但应该接近。

这是我目前在 JavaScript 中的一个(慢)实现:

// Makes a new circle
var circle = function(x,y,radius) {
    return {
        x:x,
        y:y,
        radius:radius
    };
};

// These points are not representative of the true data set. I just made them up.
var points = [
    circle(3,3,2),
    circle(7,5,4),
    circle(16,6,4),
    circle(17,12,3),
    circle(26,20,1)
];


var k = 0,
    len = points.length;
for (var i = 0; i < len; i++) {
    for (var j = k; j < len; j++) {
        if (i !== j) {
            var c1 = points[i],
                c2 = points[j],
                radiiSum = c1.radius+c2.radius,
                deltaX = Math.abs(c1.x-c2.x);

            if (deltaX < radiiSum) {
                var deltaY = Math.abs(c1.y-c2.y);

                if (deltaY < radiiSum) {
                    var distance = Math.sqrt(deltaX*deltaX+deltaY*deltaY);

                    if (distance < radiiSum) {
                        var separation = radiiSum - distance;
                        console.log(c1,c2,separation);
                    }
                }
            }
        }
    }

    k++;
}

另外,如果您用简单的英语解释一个好的算法(KD 树?),我将不胜感激:-/

【问题讨论】:

标签: algorithm nearest-neighbor


【解决方案1】:

首先,如果您跳过 SQRT 调用,上述算法将大大加快。这是比较距离的最著名的简单优化。您还可以预先计算“平方半径”距离,这样就不会重复计算它。

此外,您的某些算法中似乎还有很多其他小错误。以下是我对如何修复它的看法。

另外,如果你想摆脱 O(N-Squared) 算法,你可以看看使用kd-tree。构建 KD-Tree 需要前期成本,但其好处是可以更快地搜索最近的邻居。

function Distance_Squared(c1, c2) {

    var deltaX = (c1.x - c2.x);
    var deltaY = (c1.y - c2.y);
    return (deltaX * deltaX + deltaY * deltaY);
}



// returns false if it's possible that the circles intersect.  Returns true if the bounding box test proves there is no chance for intersection
function TrivialRejectIntersection(c1, c2) {
    return ((c1.left >= c2.right) || (c2.right <= c1.left) || (c1.top >= c2.bottom) || (c2.bottom <= c1.top));
}

    var circle = function(x,y,radius) {
        return {
            x:x,
            y:y,
            radius:radius,

            // some helper properties
            radius_squared : (radius*radius), // precompute the "squared distance"
            left : (x-radius),
            right: (x+radius),
            top : (y - radius),
            bottom : (y+radius)
        };
    };

    // These points are not representative of the true data set. I just made them up.
    var points = [
        circle(3,3,2),
        circle(7,5,4),
        circle(16,6,4),
        circle(17,12,3),
        circle(26,20,1)
    ];


    var k = 0;
    var len = points.length;
    var c1, c2;
    var distance_squared;
    var deltaX, deltaY;
    var min_distance;
    var seperation;

    for (var i = 0; i < len; i++) {
        for (var j = (i+1); j < len; j++) {
            c1 = points[i];
            c2 = points[j];

            // try for trivial rejections first. Jury is still out if this will help
            if (TrivialRejectIntesection(c1, c2)) {
                 continue;
            }



            //distance_squared is the actual distance between c1 and c2 'squared'
            distance_squared = Distance_Squared(c1, c2);

            // min_distance_squared is how much "squared distance" is required for these two circles to not intersect
            min_distance_squared = (c1.radius_squared + c2.radius_squared + (c1.radius*c2.radius*2)); // D**2 == deltaX*deltaX + deltaY*deltaY + 2*deltaX*deltaY

            // and so it follows
            if (distance_squared < min_distance_squared) {

                // intersection detected

                // now subtract actual distance from "min distance"
                seperation = c1.radius + c2.radius - Math.sqrt(distance_squared);
                Console.log(c1, c2, seperation);
            }
        }
    }

【讨论】:

  • 一路上做了一些编辑。可能是一种优化,以防止一两次冗余计算。
  • 还不错,但是如果两个圆之间的交点不是从左、右、上或下精确的。如果路口发生在任何角落,这将是无用的!
  • @Seem - 我不知道圆圈有角。谢谢!
  • :/ 我不是说圆有角!!我的意思是,如果交叉点发生在左上角、右上角、左下角或右下角,这将毫无用处。无论如何感谢您的回答:)
【解决方案2】:

这篇文章已经休眠了很长时间,但是我已经遇到并很好地解决了这个问题,所以将发布,以便其他人不必做同样的挠头。

您可以将最近圆邻居问题视为 kd 树或八叉树中的 3d 点最近邻居搜索。将两个圆 A 和 B 之间的距离定义为

D(A,B) =  sqrt( (xA - xB)^2 + (yA - yB)^2 ) - rA - rB

如果圆圈重叠,这是一个负数。对于本次讨论,我将假设一个八叉树,但 k=3 的 kd-tree 是类似的。

在每个圆的八叉树中存储一个三元组 (x,y,r)。

要找到目标圆 T 的最近邻居,请使用标准算法:

def search(node, T, nst)
  if node is a leaf
    update nst with node's (x,y,r) nearest to T
  else
    for each cuboid C subdividing node (there are 8 of them)
       if C contains any point nearer to T than nst
          search(C, T, nst)
  end
end

这里的nst 是对迄今为止找到的最接近T 的圆的引用。最初它是空的。

有点棘手的部分是确定if C contains any point nearer to T than nst。为此,只需考虑 C 内的唯一点 (x,y,r),它是在 x 和 y 中最接近 T 的欧几里得,并且具有包含在长方体中的 r 范围的最大值。换句话说,长方体表示一组圆,其中心位于 x 和 y 的矩形区域上,并具有一定的半径范围。您要检查的点是代表圆心最接近 T 且半径最大的圆。

注意 T 的半径在算法中根本没有作用。您只关心 T 的中心 在任何其他圆圈内有多远。 (我希望这在开始时就像现在看起来一样明显......)

【讨论】:

    猜你喜欢
    • 2020-08-28
    • 1970-01-01
    • 2015-01-25
    • 2016-10-31
    • 2011-11-12
    • 1970-01-01
    • 2020-01-26
    • 2017-06-25
    • 2021-07-05
    相关资源
    最近更新 更多