物体之间的最短距离

3

假设我有一个对象,它具有以下属性:

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

每个对象都有多个距离点,但只能选择一个与其他对象的距离点组合。

基于上述距离点,我可以通过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;

因此,我考虑逐个执行组合;我可以使用嵌套循环来执行,但效率非常低。

有什么最有效的方法可以实现这一点?


1
@schnaader,我已经修改了关于你提出的问题的帖子。如果有问题,请告诉我 :) - tnkh
它总是只有三个对象吗?你的对象平均有多少点?哪个数字可以变得更大? - Bergi
@Bergi,没错,所以对象的数量没有限制,每个对象中的点数也可以是任意的。但是现在假设这些数字是固定的。 - tnkh
难道解决方案不是:“对于每个盒子,选择最大的一个,然后遍历其余的盒子,找到比初始选择更低的最小值;如果没有更大的,则忽略”,这样 - 算法将执行:1. 10、9、退出;2. 9、10、12-找到,3. 14、10、退出。可能需要双向查找(即选择最低的一个,然后遍历其余的盒子找到最大的一个)。 - eithed
@eithed OP 正在尝试寻找最小距离。你的算法似乎适用于计算最大距离。 - Bergi
显示剩余3条评论
3个回答

4
对于obj o中的每个值v,创建一对(v,o),然后按照一对的第一个元素对所有一对进行排序。这需要O(n log n)。
现在,您想从排序后的序列中选择一些连续元素,其中至少有每个o中的一个元素。您可以在O(n log n)(或使用哈希图的O(n))中选择最佳答案。
从选择满足条件的最小前缀开始。建立两个指针,即连续选定元素的起点和终点。
start = 1,end = x(其中x是每个对象都在所选集合内的最小值)
然后尝试增加开始(通过删除已选子序列的第一个元素),并在必要时增加结束。 取end和start值之间差的最小值,它就是你的答案。
如我所说,为了跟踪所有对象是否在内部,创建一个映射/哈希图,在其中可以存储每个对象o在您选择的序列中的数量。 当您增加开始时,必须减少一些子序列内的对象数量。然后,您应该增加结束,直到每个对象o在所选的子序列中出现非零次数。(为了获得O(n log n)复杂度,请存储具有值0的对象数量。当您增加任何对象时,请减少计数器;当您将任何对象的数量减少到0时,请增加它)
结果总是正确的,不使用启发式方法。

你甚至不需要一个哈希表来存储对象 - 你可以给它们索引并将计数存储在数组中。 - Bergi
有时候情况并不是那么简单,但总的来说,只要你能轻松地列举对象,你就可以使用一个数组。 - Maras

4

你可以获取笛卡尔积,然后通过选取最大值和最小值之间较小差值的数组来缩减它。

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; }


2
构建笛卡尔积就是OP所说的“嵌套循环”。 - Bergi
1
我认为你的第二种方法使用了@Maras答案中详细阐述的思路 - 你应该给它点赞! - Bergi

3

我认为嵌套/递归循环是最好的算法,最坏情况下仍需要检查所有组合。但是,我认为应用分支定界算法会得到良好的优化效果,因为它会提前修剪不太好的距离分支-同时利用你已经按对象排序的点,所以您可以一次修剪多个选择。

实际上,对于每个对象,我们可以修剪为只有两个选择,即选择范围最接近且最接近当前已选择的区间。因此,我估计最坏情况下的复杂度为O(k * (2+log k)n) (给出n个平均有k个已排序点的对象),而不是朴素枚举的O(kn)

诚然,这比尼娜的第二种方法的O(kn log(kn) + kn²)或马拉斯算法的O(kn log(kn))更糟糕-可以通过在已排序列表上使用k路归并算法进一步优化至O(kn log n)


这基本上就是我想通过我的A算法建议实现的,但是A不适用于此,因为它的启发式在这里是无用的(我们无法估计进一步的成本)。 - schnaader
@גלעדברקן 是的,我想我错了 - 自从Maras发布他们的答案以来,我就有这种感觉。但我不会删除我的答案,因为我认为分支定界法有其优点。 - Bergi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接