【问题标题】:Calculate minimum value not between set of ranges计算不在一组范围之间的最小值
【发布时间】:2015-04-14 21:32:18
【问题描述】:

给定一个圆数组(x,y,r 值),我想放置一个新点,使其具有固定/已知的 Y 坐标(显示为水平线),并且尽可能靠近到中心但不在任何现有圆圈内。在示例图像中,红色的点就是结果。

圆具有已知的半径和 Y 轴属性,因此很容易计算它们在已知 Y 值处与水平线相交的点。效率很重要,我不想尝试一堆 X 坐标并针对 circles 数组中的每个项目进行测试。有没有办法在数学上计算出这个最佳 X 坐标?非常感谢任何帮助。顺便说一句,我正在使用 Raphael.js 库(因为它是唯一支持 IE8 的库)用 javascript 编写它——但这更多是一个逻辑问题,所以语言并不重要。

【问题讨论】:

  • 在左图中,为什么红点不在第四个圆(从左数)的两个交点之一?在我看来,这些点离右圆的中心比指示的点离任何圆心更近。还是“中心”有别的意思?如果有,是什么?
  • 抱歉——中心是指图中的垂直线(为简单起见,将其视为 X=0)
  • “尝试一堆 X 坐标”是什么意思?您只需尝试交叉点。

标签: javascript math trigonometry intervals


【解决方案1】:

我会按如下方式处理您的问题:

  1. 初始化一组区间S,按区间X坐标排序,为空集
  2. 对于每个圆c,计算c与X轴的交点Ic的间隔.如果 c 不相交,则继续下一个圆圈。否则,测试 Ic 是否与 S 中的任何间隔重叠(这很快,因为 S 是排序);如果是这样,从 S 中删除所有相交区间,将 Ic 和所有删除的区间折叠成一个新区间 I' c 并将 I'c 添加到 S。如果没有交叉点,则将 Ic 添加到 S
  3. 检查 S 中的任何区间是否包括中心(再次,快速,因为 S 已排序)。如果是,则选择离中心最近的区间端点;如果不是,则选择中心作为最近点。

【讨论】:

  • +1 用于折叠间隔,这似乎是 OP 没有想到的。顺便说一句,您可以在计算所有交点时检查任何区间是否包括中心,甚至在对它们进行排序之前。
  • @Bergi - 关于跟踪哪个区间(如果有)包含中心的好点。由于合并,最多可以有一个这样的区间。
  • 感谢 Ted,这看起来很有希望,明天将在 JS 中尝试。请原谅我的无知,但是当您提到“一组间隔”时,您是指一组 Xstart 和 Xend 值(即沿 x 轴的范围)吗? 'collapse Ic...' 是指,例如,如果一个是 1
  • @Dave - 是的,每个区间都应该代表沿 x 轴的连续范围。关于崩溃,你描述的正是我的意思。另一个术语可能是“合并”区间。
【解决方案2】:

基本上圆的方程是(x - cx)2 + (y - cy)2 = r2。因此,您可以通过将 y 替换为 0 轻松找到圆和 X 轴之间的交点。之后,您只需解决一个简单的quadratic equationx2 - 2cxx + cx2 + cy2 - r2 = 0 。为此,您有 3 种可能的结果:

  • 没有交集 - 行列式将是无理数(JavaScript 中的 NaN),忽略此结果;
  • 一个交集 - 两个解决方案都匹配,使用 [value, value];
  • 两个交点 - 两种解决方案不同,请使用 [value1, value2]

对新计算的交叉区间进行排序,然后尝试在可能的地方合并它们。但是请记住,在每种程序语言中都有近似值,因此您需要为点近似值定义增量值,并在合并区间时将其考虑在内。

当间隔合并时,您可以通过在每个间隔的开始/结束处减去/添加相同的增量值来生成 x 坐标。最后,从所有方面来看,最接近零的就是你的答案。

这里是一个 O(n log n) 复杂度的例子,它以可读性为导向。我用 1*10-10 表示 delta :

var circles = [
    {x:0, y:0, r:1},
    {x:2.5, y:0, r:1},
    {x:-1, y:0.5, r:1},
    {x:2, y:-0.5, r:1},
    {x:-2, y:0, r:1},
    {x:10, y:10, r:1}
];

console.log(getClosestPoint(circles, 1e-10));



function getClosestPoint(circles, delta)
{
    var intervals = [],
        len = circles.length, 
        i, result;
    for (i = 0; i < len; i++)
    {
        result = getXIntersection(circles[i])
        if (result)
        {
            intervals.push(result);
        }
    }

    intervals = intervals.sort(function(a, b){
        return a.from - b.from;
    });
    if (intervals.length <= 0) return 0;
    intervals = mergeIntervals(intervals, delta);

    var points = getClosestPoints(intervals, delta);
    points = points.sort(function(a, b){
        return Math.abs(a) - Math.abs(b);
    });
    return points[0];
}

function getXIntersection(circle)
{
    var d = Math.sqrt(circle.r * circle.r - circle.y * circle.y);
    return isNaN(d) ? null : {from: circle.x - d, to: circle.x + d};
}

function mergeIntervals(intervals, delta)
{
    var curr = intervals[0],
        result = [],
        len = intervals.length, i;
    for (i = 1 ; i < len ; i++)
    {
        if (intervals[i].from <= curr.to + delta)
        {
            curr.to = Math.max(curr.to, intervals[i].to);
        } else {
            result.push(curr);
            curr = intervals[i];
        }
    }
    result.push(curr);
    return result;
}

function getClosestPoints(intervals, delta)
{
    var result = [], 
        len = intervals.length, i;
    for (i = 0 ; i < len ; i++)
    {
        result.push( intervals[i].from - delta );
        result.push( intervals[i].to + delta );
    }
    return result;
}

【讨论】:

  • 这与我提出的方法大致相同,但不是 O(n) 复杂性。生成间隔后,在合并之前对它们进行排序,这是一个 O(n log n) 操作。此外,尚不清楚是否建议悲观地使用delta(实际上是扩大间隔)。计算错误同样可能是正数或负数,因此可以很容易地提出一个论点,即间隔应该缩小delta,而不是扩大。从概率上讲,最好简单地使用平均 delta 值,即 0。
  • 感谢您的指点,我完全错过了复杂度评估中的排序,现在更新了。我可能会错误地传达delta 角色,它用于将点放在圆形区域之外,因为如果您采用间隔边缘,您将位于圆形与轴相交的点,据我所知,确实如此不满足条件。
  • 总的来说,这是一个不错的解决方案(包含代码,我毫不怀疑,它为您赢得了当之无愧的复选标记)。关于delta,OP 想要一个“不在”任何圈子内的点。我将其解释为不在任何圆的 interior 中,这将允许圆周上的点。在测试是否合并时,您还使用delta 扩大间隔,这让我觉得不合理。我会简单地消除它,因为它增加了复杂性并且(充其量)具有可疑的理论基础。
  • 是的,如果允许圆周上的点,我同意需要删除 delta。但是,如果不是,则有一种极其罕见但可能的情况,其中两个间隔之间的差异可能正好是 delta(或者如果 delta 不是系统可以区分的最小可能单位,则可能更小),这会给我们错误的解决方案.
  • 好吧,我认为使用 delta 会带来相同(如果不是更大)给出错误解决方案的风险(其中两个区间之间的距离在数学和计算上都是正数,但在计算上小于 delta) .在这种情况下,使用delta 将导致合并应该(并且应该)保持不同的区间。
【解决方案3】:
  1. 创建 intersect_segments 数组(在 x=0 y=0 处归一化)

  2. 按上限对相交段进行排序,并删除上限

  3. 初始化 point1 = 0 和 segment = 0

  4. 当 point1 在 intersectsegment[segment] 内时循环

    4.1。通过 intersectsegment[segment] 的上限增加 point1

    4.2。增量段

  5. 按下限对相交段进行排序,并删除 loerlimit>0

  6. 初始化 point2 = 0 和 segment = 0

  7. 当 point2 在 intersectsegments[segment] 内时循环

    7.1。将point2递减段的下限

    7.2。递减段

  8. 点是p1和p2的最小绝对值

【讨论】:

  • 我认为在第 2 步中删除上限
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-11-09
  • 1970-01-01
  • 2020-06-05
  • 2023-01-15
  • 2021-11-27
  • 1970-01-01
  • 2021-12-20
相关资源
最近更新 更多