对于我校的ACM-ICPC团队,我们一直难以理解如何解决这个具体的练习问题:
给定N个节点位于坐标(X {i},Y {i}),计算从节点1,2,3,...,N遍历所需的最短曼哈顿距离,然后再回到1(在那个命令)。您只能访问每个节点一次。如果这样的路径不存在,则输出-1 N以[1,100]为界 X和Y坐标以[1,100,000,000]
为界
我们已经提出了一些样本数据:
N = 4
坐标:
1.(2,2)
2.(2,4)
3.(2,1)
4.(1,3)
在这种情况下,最短路径是从节点1到节点2的12:2单位,从节点2到节点3的5个单位(绕过节点1,在路上),从节点3到节点的3个单位4,然后2个单位返回节点1.
虽然可能是哈密顿循环。我还认为构建“图形”是问题的主要部分。一旦构建了图形,您就可以走它以找出总体最短距离。我的问题是检测从i到i + 1的路径是否包含一个节点,如果有,那么绕过它的最佳方法是什么(知道重新路由可能还有一个节点)?
UPDATE :我有O(N ^ 2)代码来检测2个节点是否在同一行,diagnol或列中,但我不知道重新路由它的方法是什么。
答案 0 :(得分:5)
(我已经扩展了这个答案来描述一个不同的,希望更快的算法。结果,一些评论有点过时了。)
首先,我会尝试澄清问题:
我们有一个大格子。 100,000,000次100,000,000个节点。这是很多节点。这些节点中的少数N是给定节点。我们要在格子中找到一个循环。循环不必访问晶格中的每个节点,但它必须只访问每个给定节点一次。它必须按给定的顺序访问那些给定节点。
解决方案:
我们必须找到N条最短路径。我们必须找到:
g_1
到g_2
的最短路径,避开所有其他给定节点 g_2
到g_3
的最短路径,避开所有其他给定节点 g_{N-1}
到g_N
的最短路径,避开所有其他给定节点 g_N
到g_1
的最短路径,避开所有其他给定节点 这些问题彼此独立。我稍后会尝试描述一种快速算法,但首先这里是一个简单的算法来查找从给定源到给定目标节点的路径:
这不是一个快速的算法,但我希望它是正确的。它会比它更慢很多。我们可以改进标准的BFS最短路径算法,因为我们不处理任何任意图形,我们正在处理一个格子。
(我认为这是一个标准的A* search算法。感谢我在大学的同事指出这一点。)
假设你到目前为止,我现在的目标是找到格子上两个节点之间的最短路径,其中(小)节点不可用。
想象一下,格子中的每个可用节点都有两个与之关联的变量,一个“上限”。和一个下限'这些边界描述了返回源节点的距离的上限和下限。我们将使用乐观的'来初始化下限。回到源节点的曼哈顿距离('乐观'因为我们允许使用所有节点)。我们将所有节点的上限初始化为无限,除了指定上限为0的源节点。对于单个节点,一旦其上限已收敛到其下限,则这是到达其的上限的正确距离。源节点。算法继续,逐渐增加下限并减小上限,直到目标节点的边界收敛为止。
开始时,边界已经为源节点收敛。从源到自身的距离只是0。
(显然,为所有节点存储所有这些数字对是不切实际的,但我们稍后会回答这个问题。)
我们如何更新界限?鉴于目前的任何'节点,其邻居的上限不应超过当前节点的上限1。这允许节点下拉'邻居的上限。
此外,如果两个相邻节点的下限存在较大差异,则它们可能会相互影响。如果节点A的下限为5并且它连接到节点B,其下限为3,那么我们可以将B的下限增加到4.通过矛盾证明:如果B 可以只需3步即可到达,然后通过B进入最多4步就可以达到A.但这与我们的知识相矛盾,即A的下限为5.因此,B的下限应增加到4。
这显示了节点如何能够拉起或压下他们的邻居'界限。我们必须将这个想法变成一种算法。
我们需要保持一个前沿'节点这个边界是一组有可能影响其邻居的节点。界限。最初,边界是一个单独的集合,只包含源节点。
循环执行以下操作,直到边界收敛到目标节点:
最后,我们必须设计一个合适的数据结构来存储这些边界。为这个巨大的格子的所有点明确存储它是不可行的。我会使用C ++中的地图
// mapping from (x,y) to (upper_bound, lower_bound)
map< pair<int,int>, pair<int,int> > bounds;
最初,此地图仅包含源节点:(source_x, source_y) => (0,0)
当尝试查询当前不存在的节点的此映射时,我们可以如上所述初始化它,并使用上限的乐观Manhatten分数和上限的无穷大(INT_MAX
)。请记住,算法只会查询与边界节点相邻的节点,因此在许多情况下它不应该变得太大。此外,如果某个节点已收敛,并且其所有邻居已收敛(或无法使用),那么我们可以将其从地图中永久删除。