【问题标题】:Shortest distance between objects物体之间的最短距离
【发布时间】:2019-07-22 10:30:33
【问题描述】:

假设我有一个这样的对象:

let objs = { obj1: [ 0, 10 ], obj2: [ 3, 9 ], obj3: [ 5, 12, 14 ] }

每个obj有多个距离点,但只能选择一个与其他obj的距离点结合。

我可以根据上面的距离点以 12 种方式组合三个对象。

比如可以变成[0,3,5]; 在这种情况下,三个对象之间的总距离将是 5 - 0,即 5;

或者可以变成[10, 9, 5],距离为10 - 5 = 5;

组合也可以是[0, 3, 12],距离为12 - 0 = 12;

我打算实现的是找到最短的组合,在这种情况下应该是[10,9,12];距离为12-9 =3;

所以我想到了一个一个的组合;我可以做嵌套循环来做到这一点,但它会非常低效。 我可以用来实现这一目标的最有效方法是什么?

【问题讨论】:

  • 两件小事:“最短”在这里不清楚(最短的运行时间?/最短的代码?/...)另外 - 你知道存在 O(n) 方法吗?如果没有,则可能没有,这将是一个无法实现的约束。
  • @schnaader,我已就您的问题对我的帖子进行了修改。如果您有问题,请告诉我:)
  • 总是只有三个对象吗?你的对象平均有多少分?哪个数字可以变得更大?
  • @Bergi,对,所以对象的数量没有限制,每个对象的点数也可以是任意的。但是让我们假设这些数字现在是固定的。
  • 解决方案不是:“foreach 框选择最大的一个,然后遍历其余的框,找到最低的一个,这比最初选择的要大;如果没有更大的,则忽略”,因此 - 算法会: 1. 10, 9, 退出; 2. 9, 10, 12 - 找到,3. 14, 10,退出。可能需要双向查看(即选择最低的,然后通过休息找到最大的)

标签: javascript algorithm


【解决方案1】:

您可以得到笛卡尔积,然后通过取最大值和最小值增量较小的那个来减少数组。

let objects = { obj1: [ 0, 10 ], obj2: [ 3, 9 ], obj3: [ 5, 12, 14 ] },
    data = Object.values(objects),
    cartesian = data.reduce((a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), [])),
    result = cartesian.reduce((a, b) => Math.max(...a) - Math.min(...a) < Math.max(...b) - Math.min(...b) ? a : b)

console.log(result);
console.log(cartesian.map(a => a.join(' ')));
.as-console-wrapper { max-height: 100% !important; top: 0; }

一种更快的方法使用排序数组,并且仅按排序顺序获取每个数组的最后三项。

这是不使用笛卡尔积的线性搜索。

index  values                delta
-----  --------------------  -----
   0    0          10 
   1       3     9
   2          5       12 14
       --------                 5
       --    -----              9
             --------           5
                --------        3 <-- min
                ------   --     5

let objects = { obj1: [ 0, 10 ], obj2: [ 3, 9 ], obj3: [ 5, 12, 14 ] },
    data = Object
        .values(objects)
        .map((a, i) => a.map(v => [v, i]))
        .reduce((a, b) => a.concat(b))
        .sort((a, b) => a[0] - b[0] || a[1] - b[1]),
    parts = [undefined, undefined, undefined],
    result,
    i;

for (i = 0; i < data.length; i++) {
    parts[data[i][1]] = data[i][0];
    if (parts.some(v => v === undefined)) continue;
    if (!result || Math.max(...parts) - Math.min(...parts) < Math.max(...result) - Math.min(...result)) {
        result = parts.slice();
    }
}

console.log(result);
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }

【讨论】:

  • 构造笛卡尔积正是 OP 所说的“嵌套循环”。
  • 我认为您的第二种方法使用了@Maras 回答中详述的想法 - 您应该支持它!
【解决方案2】:

为 obj o 中的每个值 v 创建对 (v, o),然后按对的第一个元素对所有对进行排序。它需要 O(n log n)

现在你想从排序序列中选择一些连续的元素,每个 o 中至少有一个在其中。您可以在 O(n log n) (或 O(n) 使用哈希映射)中选择最佳答案

从选择满足条件的序列的最小前缀开始。做两个指针,你连续选择的元素的开始和结束。

开始 = 1,结束 = x(其中 x 是每个对象在所选集合内的最小值)

然后尝试增加开始(通过删除所选子序列的第一个元素)并在必要时增加结束。 取最小的结束值和开始值之间的差异,这就是你的答案。

正如我所说,要跟踪是否所有对象都在其中,请创建一个映射/哈希映射,您可以在其中存储每个对象的数量或您选择的序列中的数量。 当你增加 start 时,你必须减少子序列中一些对象的数量。然后你应该增加 end 直到每个对象 o 在所选子序列中出现非零次。 (要获得 O(n log n) 复杂度,存储多少个对象的值为 0。当你增加 any 时,减少计数器,当你将任何对象的数量减少到 0 时,增加它)

结果总是正确的,没有使用启发式方法。

【讨论】:

  • 您甚至不需要对象的(散列)映射 - 您只需为它们提供索引并将计数存储在数组中即可。
  • 有时没那么简单,但总的来说是的,只要你可以轻松枚举对象,你就可以使用数组
【解决方案3】:

我认为没有什么比嵌套/递归循环更好的了,在最坏的情况下,您仍然需要检查所有组合。但是,我认为应用 branch-and-bound algorithm 会提供很好的优化,因为它会尽早修剪距离不太好的分支 - 还可以利用每个对象的点已排序这一事实,因此您可以一次修剪多个选择.

实际上,对于每个对象,我们可以只修剪到 2 个选项,它们分别是目前所选区间的最下方和最上方。因此,我估计最坏情况下的复杂度为 O(k * (2+log k)<sup>n</sup>)(给定 n 对象,平均 k 已排序点),而不是简单枚举的 O(k<sup>n</sup>)

诚然,这比 Nina 的第二种方法的 O(kn log(kn) + kn²) 或 Maras 的算法的 O(kn log(kn)) 更糟糕 - 通过在已排序的列表上使用 n-way merge 可以进一步改进为 O(kn log n)

【讨论】:

  • 这基本上是我想通过我的 A* 算法建议实现的目标,但 A* 不合适,因为它的启发式在这里没有用(我们无法估计进一步的成本)。
  • @גלעדברקן 是的,我想我错了 - 自从 Maras 发布他们的答案后,我就有这种感觉。我仍然不会删除我的答案,因为我认为分支定界有其优点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-18
  • 1970-01-01
  • 2018-03-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多