我有一个城市地图(2D)和整个城市的随机点。其中一些随机点是游客可以下车的地方。从这些下降点开始,我需要为游客在之前访问过的点的X长度(比如英里)内无法到达的所有其他点进行着色。因此,只要它们在X距离内的每一跳足够接近,就可以链接来自衰减的点。同样,所有随机源和目的地,所以没有确定的方向图。
但是,我觉得它可能是NP-Hard。这是对的吗?
我不想要最短的路径,所以我觉得这样可以消除Dijkstra和我可以使用的一些不同的图算法选项。
使用各种修剪进行蛮力BFS搜索并不是很明显。在一定数量的随机点之后,生成所有潜在邻居的过程太复杂了。我正在考虑Floyd-Warshall或一些变体,并且标记点不能从我的来源中触及,但因为这些点彼此之间没有连接,这也需要花费大量的时间和内存来计算所有可能的每个相关节点的邻居。
我是否过于复杂?也许以某种方式反转问题可能有所帮助 - 从所有随机节点遍历到源节点作为"目的地"?
答案 0 :(得分:2)
我建议使用宽度为R * R的方形网格网格,其中R是给定的恒定距离。
将所有点放入这些存储桶中,然后只需检查以该点为中心的3 * 3存储桶阵列,即可快速回答“查找当前点距离R内的所有点”形式的查询。 / p>
然后,您可以使用BFS为源点范围内的所有点着色,但它应该更快,因为桶应该意味着您需要在每个阶段考虑更少的潜在邻居。
(顺便提一下,你的原始方法也是多项式时间,所以这个问题不是NP难的。)
答案 1 :(得分: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是随机数points和s是起点的数量。但是要明白,最坏的情况只会出现在一个非常稀疏的图表中,或者当大量的点无法到达时。
请注意&#34;从未知列表中移除unknown_point&#34;如果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)
}
}
通过合并&#34; binning&#34>,您可以降低预期情况的复杂性。 Peter de Rivaz推荐的优化。这限制了您必须通过将搜索限制到相邻区域来检查每个边界点的点数:唯一可能无法到达未知点的位置。基本上,您创建网格以覆盖所有随机点。类似的东西:
0 1 2 3 4 5
-------------------------------------------------------
| .. | | . . | | | |
A | . . | | . . | . | | . . |
| . | | . | . . | | . |
-------------------------------------------------------
| | . .| | . . . | |. |
B | | . | . | . . | | |
| | . | | . | | .|
-------------------------------------------------------
| . . | | . | | | . |
C | . | | . | | | . |
| | | . | | | . |
-------------------------------------------------------
| . | | . . | . | . . | . . |
D | | | . | . | . | . . . |
| . | | . | | . | . |
-------------------------------------------------------
| |. . | . . | | | |
E | | . | . | | | |
| | . | . . | | | |
-------------------------------------------------------
| | . | | . . | . . | . . |
F | | . | | . . | . . | . . |
| | . . | | . | . | . |
-------------------------------------------------------
如果您的距离阈值为dist
,那么每个网格方块的每一侧都是dist
个单位。
然后,我们知道,网格B3中的一个点只能在九个相邻正方形的dist
个点内。因此,我们不必测试网格F5中的点数。请注意,并非所有A3中的点都必须可以从B3中的某个点到达,但它们可以是。事实上,我们无法保证B3中的每个点都与B3中的每个点相邻。考虑一个仅包含两个点的网格:一个位于极左上角,一个位于极右下方。这两点之间的距离将超过dist
。
根据点的密度,您可能需要某种稀疏的数据结构。
你要做的第一件事是随机点。通过随机点来查找最顶部和最左侧的坐标。这成为你的起点。 Bin A0的左上角位于(最顶部,最左侧)。然后,您可以浏览所有随机点并将其添加到容器中。
之后,算法关注的是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
}