【问题标题】:Is this Multiple Sources Multiple Destinations algorithm NP hard?这个 Multiple Sources Multiple Destinations 算法 NP 难吗?
【发布时间】:2017-11-30 10:30:13
【问题描述】:

我有一张城市地图 (2D) 和整个城市的随机点。其中一些随机点是游客可以下车的地方。从这些下车点,我需要为游客在距离先前访问的点 X 长度(例如英里)内无法到达的所有其他点着色。因此,只要它们在 X 距离内的每一跳足够接近,就可以链接来自 dropoff 的点。同样,所有的来源和目的地都是随机的,所以没有既定的方向图。

但是,我觉得它可能是 NP-Hard。这是正确的吗?

我不想要最短路径,所以我觉得这消除了 Dijkstra 和我可以使用的一些不同的图形算法选项。

具有各种修剪的蛮力 BFS 搜索显然不会走得太远。在一定数量的随机点之后,生成所有潜在的邻居太复杂了。我正在考虑 Floyd–Warshall 或一些变体,并将点标记为从我的来源中不可触及,但由于这些点彼此之间没有联系,这也将花费大量时间和内存来计算所有可能的每个相关节点的邻居。

我是否过度复杂化了复杂性?也许以某种方式反转问题可能会有所帮助 - 从所有随机节点到作为“目的地”的源节点?

【问题讨论】:

  • 只要没有负权重边缘,Dijkstra 就可以正常工作;不过,您需要为所有来源运行它。一个有趣的转折是,如果距离太远,您似乎无法访问节点,这实际上意味着您获得的边是无效的,不应考虑进行此搜索。
  • 我认为它会,但开始时没有优势。它们必须被生成。当有数千个潜在边缘时,它会迅速增加。我想知道是否使用了某种差异。
  • 在任何 n 个顶点的简单图中最多有 n(n-1)/2 条边。那不是很多。至少不能说它是 NP 难的。
  • @JuanLopes 顶点的数量并不能真正告诉您问题是否是 NP 难题。毕竟旅行商问题中只有(n^2 - n)/2个顶点,而且是NP-hard。

标签: algorithm


【解决方案1】:

见下文进行优化

我认为你把事情复杂化了。蛮力方法会将每个点与其他点进行比较。最坏的情况是 O(r*(s+r)),其中 r 是随机点的数量,s 是起点的数量。

在所有(或大部分)点都可到达的预期情况下,您可以使用队列来降低复杂性。这个想法是,一旦你确定一个点是可到达的,你就不必再检查它是否可以从其他点到达。但是,您必须检查是否可以从它到达其他点。

当你开始时,你所有的随机点都是“未知的”。也就是说,他们从未被访问过。但是一旦访问了一个点,它就不再是未知的:我们知道它可以到达。因此,当您第一次访问某个点时,您会将其从未知领域移至前沿。然后你穿过边界,在未知中寻找触手可及的点。

大体思路是:

unknown = list of random points
frontier = new queue()
add all source cells to frontier
while (!unknown.isEmpty() && !frontier.isEmpty())
{
    point = frontier.dequeue()
    for each unknown_point
    {
        if (dist(point, unknown_point) < distance)
        {
            remove unknown_point from unknown list,
            and add to frontier queue
        }
    }
}

if (!unknown.IsEmpty())
{
    // there are still points in the unknown,
    // which means that not all points are reachable.
}

在最坏的情况下,该算法将针对每个随机点测试每个起点,并针对每个其他随机点测试每个随机点,因此复杂度为 O(r*(s + r)),其中 r 是随机数点和s是起点的数量。但请理解,最坏的情况只会出现在非常稀疏的图形中,或者无法到达大量点时。

请注意,如果unknown 是一个常见的列表数据结构或数组,“从未知列表中删除未知点”本身可能是一个 O(r) 操作。一个有用的优化是使unknown 成为一个队列,并像这样修改你的内部循环:

    point = frontier.dequeue()
    unknown_count = unknown.count()
    while (unknown_count > 0)
    {
        unknown_point = unknown.dequeue()
        --unknown_count
        if (dist(point, unknown_point) < distance)
        {
            // within range, add to frontier
            frontier.enqueue(unknown_point)
        }
        else
        {
            // not reachable. Put it back in the unknown.
            unqnown.enqueue(unknown_point)
        }
    }

优化

您可以通过合并 Peter de Rivaz 推荐的“分箱”优化来降低预期情况的复杂性。这通过将搜索限制在相邻的 bin 中来限制您必须为每个边界点检查的点数:这是唯一可能到达未知点的地方。基本上,您创建网格以覆盖所有随机点。比如:

       0        1        2        3        4        5
   -------------------------------------------------------
   |  ..    |        | .  .   |        |        |        |
A  |   . .  |        |  .  .  |  .     |        |  . .   |
   |  .     |        |    .   | .   .  |        |   .    |
   -------------------------------------------------------
   |        | .     .|        |  . . . |        |.       |
B  |        | .      |    .   | .  .   |        |        |
   |        |      . |        |   .    |        |       .|
   -------------------------------------------------------
   | .   .  |        |   .    |        |        | .      |
C  |   .    |        | .      |        |        |   .    |
   |        |        |     .  |        |        |   .    |
   -------------------------------------------------------
   |    .   |        | .  .   |  .     |   . .  | .  .   |
D  |        |        |   .    |    .   |   .    | . . .  |
   |     .  |        |     .  |        |     .  |  .     |
   -------------------------------------------------------
   |        |.   .   |  .   . |        |        |        |
E  |        |  .     |    .   |        |        |        |
   |        |     .  |  .  .  |        |        |        |
   -------------------------------------------------------
   |        |     .  |        | .  .   |  .  .  |  .  .  |
F  |        |  .     |        |  .  .  | .  .   | .  .   |
   |        | .  .   |        |   .    |      . |      . |
   -------------------------------------------------------

如果您的距离阈值是dist,那么每个方格的每边都是dist 个单位。

那么,我们知道,网格 B3 中的一个点只能在九个相邻方格中的dist 个点的单位内。所以我们不必针对网格 F5 中的点进行测试。请注意,并非 A3 中的所有点都一定可以从 B3 中的某个点到达,但它们可能。事实上,我们不能保证 B3 中的每个点都与 B3 中的每个其他点相邻。考虑一个只包含两个点的网格:一个在最左上角,一个在最右下角。这两点之间的距离将大于dist

根据点的密度,您可能需要某种稀疏数据结构来存储箱。

您要做的第一件事是将随机点装箱。穿过随机点以找到最顶部和最左侧的坐标。这成为你的原点。 Bin A0 的左上角位于 (topmost, leftmost)。然后,您可以遍历所有随机点并将它们添加到 bin。

之后,算法专注于 bin,而不是随机点数组:

frontier = new queue()
add source points to frontier
while (!allBinsAreEmpty() && !frontier.IsEmpty())
{
    point = frontier.dequeue()
    sourceBin = determine bin that point is in
    adjacentBins = getAdjacentBins(sourceBin.x, sourceBin.y)
    for each adjacent bin
    {
        for each binPoint in bin
        {
            if distance(point, binPoint) <= dist
            {
                frontier.enqueue(binPoint)
                bin.Remove(binPoint)
            }
        }
        if (bin is empty)
            remove bin
    }
}
if (!allBinsAreEmpty())
{
    // there are unreachable points
}

获取相邻的垃圾箱非常简单:

getAdjacentBins(binx, biny)
{
    adjacentBins[] = [bins[binx, biny]]
    if (bins[binx-1, biny-1] != null) adjacentBins += bin[binx-1, biny-1]
    if (bins[binx-1, biny] != null) adjacentBins += bin[binx-1, biny]
    if (bins[binx-1, biny+1] != null) adjacentBins += bin[binx-1, biny+1]
    if (bins[binx, biny+1] != null) adjacentBins += bin[binx, biny+1]
    ....
    return adjacentBins
}

【讨论】:

  • 我看到你更新了你的答案,我打算回应说我尝试了一种标记未知/已知点的就地方法(与队列相反,但实际上仍然相同),但是仍然不够快。我会查看相邻的垃圾箱建议,我最初对如何构建垃圾箱感到困惑,但你清除了它。
  • @user999999928:就地标记仍然需要您查看每个点以查看它是否已被标记。不用每次都做距离计算,但还是要看点的。在预期的情况下,队列将更有效率。但是,是的......垃圾箱应该更好。
  • 优化更有意义。我还没有尝试过,但是是否有特殊的理由将您建议的 2D 设置用于箱(X 和 Y 键)与 1 键(X&Y 以某种方式散列)?我一直在阅读不同类型的分箱,但我不知道你什么时候会使用其中一种而不是另一种。我还读到,这取决于随机点彼此之间(或相距甚远)的堆积程度,尽管我们正在寻找相隔恒定的 X 个单位,但我们需要更紧密的 bin 距离。这与 2D 分箱有关吗?
  • 垃圾箱在二维网格中,所以我就是这样称呼它们的。您的代码如何散列密钥是一个实现细节。如果您使用数组来计算垃圾箱,您将使用两个坐标。如果您使用的是稀疏数据结构,您将使用某种散列。但是,您实施它不应该对运行时间产生重大影响。正如我所说,如果你的点是稀疏的——很多空箱——使用稀疏的数据结构。否则使用数组。
【解决方案2】:

我建议使用宽度为 R*R 的方形桶网格,其中 R 是给定的恒定距离。

将所有点放入这些桶中,然后您只需检查以该点为中心的 3*3 桶数组,即可非常快速地回答“查找当前点距离 R 内的所有点”形式的查询。

然后您可以使用 BFS 为源点范围内的所有点着色,但它应该更快,因为桶应该意味着您需要在每个阶段考虑更少的潜在邻居。

(顺便说一句,你原来的方法也是多项式时间,所以这个问题不是 NP 难的。)

【讨论】:

  • 这个有名字吗?这似乎是迭代生成邻居的好方法,但是“将所有这些点放入这些桶中”是什么意思?
  • 例如,假设我们通过为每个存储桶设置一个链表来实现这一点。当您在桶中放置一个点时,这意味着将该点添加到该位置的链表中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-04-20
  • 2014-12-08
  • 2012-11-16
  • 2015-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多