在动态有向图中查找最小循环路径

时间:2012-11-02 16:48:52

标签: algorithm graph-algorithm

我最近遇到了来自今年早些时候Spotify的黑客挑战的this (Edit: Problem A)有趣的问题,该问题涉及确定在火车卡车路口的切换以将火车送回到它的起点。火车必须朝向与它相同的方向到达,火车永远不会在轨道上倒转。

据我了解,问题可以建模为无向(?)图,我们必须从某个顶点找到最短的周期,或者检测到没有这样的周期。然而,有趣的是,对于顶点v,与v相邻的顶点取决于采用v的路径,因此在某种意义上,图形可以被认为是有向的,尽管这个方向是路径依赖的。

我的第一个想法是将每个节点建模为3个独立的顶点,A,B和C,其中A< - > B和A - < - > C,然后使用广度优先搜索来构建搜索树,直到我们找到原始顶点,但这由上面的警告复杂化,即给定顶点的邻接依赖于我们访问的前一个顶点。这意味着在我们的BFS树中,节点可以有多个父节点。

显然,简单的BFS搜索不足以解决这个问题。我知道存在用于检测图中的循环的算法。一种方法可能是检测所有周期,然后对于每个周期,检测路径是否有效。 (即,不反转方向)

是否有其他人对解决此问题的方法有任何见解?

更新 我按照@Karussell在评论中提出的方法。

Here is my solution on github.

诀窍是使用基于边缘的图形来模拟情况,而不是传统的基于顶点的图形。比赛中提供的输入文件已经根据边缘方便地指定,因此该文件可以很容易地用于构建基于边缘的图形。

该程序使用两个重要的类:Road和Solver。道路有两个整数字段,j1和j2。 j1表示源结,j2表示目标结。每条道路都是单向的,这意味着你只能从j1到j2旅行。每条道路还包括相邻道路的链表和父道。 Road类还包括静态方法,用于在输入文件中使用的字符串和表示每个连接处的A,B和C点的整数索引之间进行转换。

对于输入文件中的每个条目,我们向HashMap添加两个Road,两个结点之间的每个方向都有一个Road。我们现在列出了在路口之间运行的所有道路。我们只需要通过A,B和C开关将道路连接在一起。如果道路在Junction.A结束,我们会查看从Junction.B和Junction.C开始的道路,并将这些道路添加为邻接区域。 buildGraph()函数返回其目标连接点(j2)为“1A”==索引0的Road。

此时,构建了我们的图表。为了找到最短的路径,我只使用BFS来遍历图形。我们保留根没有标记,并开始排队根的邻接。如果我们找到一条目标路口为“1A”(索引0)的道路,那么我们找到了通过起点的最短周期。一旦我们使用每个Road的父属性重建路径,根据问题的要求适当设置开关是一件小事。

感谢Karussell建议采用这种方法。如果你想把你的评论放在答案表中,并附上简短的解释,我会接受。还要感谢@Origin。我必须承认,我没有完全遵循你答案的逻辑,但这肯定不是说它不正确。如果有人使用您的解决方案解决了这个问题,我会非常有兴趣看到它。

2 个答案:

答案 0 :(得分:1)

一种可能的方法:首先构建某种图形来模拟所有连接(图G)。然后构建另一个图,我们将在其中找到循环(图H)。对于G中的每个节点A,我们将向图H添加节点。每个A节点还具有2个输出边(对于图G中的B和C节点)。在H中,这些边缘将转到将在G中遇到的下一个A节点。例如,H中对应于具有ID 3的交换机的A节点的A节点将具有到节点9和节点6的出口边缘。 H.每条边的重量是在该路线上传递的开关数(包括起动开关)。

这将生成一个图表,我们可以在其中生成前向最短路径树。如果我们再次开始,那么周期就会完成。

关键是如果交换机在A->中被遍历,则交换机只是一个决策点。方向。没有必要对后向进行建模,因为这只会使搜索复杂化。

编辑:更多澄清

问题在于确定从A到A的最短路径(再次)。最短的定义是这里传递的开关数量。这将用于基于Dijkstra的搜索算法。我们基本上是在图H上做Dijkstra,其中边的成本等于该边的开关数。

在H图中,我们将为每个开关提供一个节点。每个节点将有2个输出边,对应于可以采用的2个路径(B和C方向)。 H中的边将对应于原始图中的2个A节点之间的整个路径。对于问题描述中的示例,我们得到以下内容:

与开关1对应的节点:

  • 1到节点2的输出链路,权重2,对应于取C 离开开关1的方向。重量为2,因为如果我们从A1-> C1-> C3-> A3-> A2
  • ,我们通过开关1和开关3
  • 1到节点3的输出链接,权重2,对应于采用B方向

与交换机2对应的节点:

  • 1到节点6的输出链路,权重2,对应于采用B方向
  • 没有第二个链接,因为C方向是死路

与开关3对应的节点:

  • 1个到节点6的输出链接,权重2,对应于采用C方向
  • 1个到节点9的输出链路,重量3,对应于取B方向并通过开关3,7和8
每个开关都是

等等。这产生了一个包含10个节点的图形,每个节点最多有2个有向边。

现在我们可以开始构建我们的Dijkstra树了。我们从节点1开始,有两个可能的方向,B和C.我们把它们放在优先级上。然后队列包含[节点2,权重2]和[节点3,权重2],因为我们可以在通过2个交换机之后到达交换机2的A入口,并且在通过2个交换机之后到达交换机3的A入口。然后我们通过从队列中获取最低权重条目继续搜索:

  • [节点2,权重2]:只采取B方向,所以将[节点6,权重4]放在队列上
  • [节点3,权重2]:2个方向,所以将[节点6,权重4]和[节点9,权重5]添加到队列中。
  • [节点6,权重4]:可能有2个方向,将[节点4,权重5]和[节点8,权重8]添加到队列中]
  • [节点9,权重5]:仅C方向,添加[节点10,权重6]
  • [节点4,权重5]:为C方向添加[节点5,权重7],为B方向添加[节点1,权重9]]
  • [节点10,权重6]:为C方向添加[节点1,权重8],为B方向添加[节点1,权重10]
  • [节点5,重量7]:添加[节点1,重量11]和[节点8,重量10]
  • [节点8,权重8]:添加[节点7,权重9]
  • [节点1,重量8]:我们回来了,所以我们可以停止

(错误是可能的,我只是手工完成)

然后算法以一个循环的最终长度8停止。确定跟随的路径只是在你解决它们并解压缩路径时维护节点的父指针。

我们可以使用Dijkstra,因为H中的每个节点对应于在正确的方向上遍历原始节点(在G中)。然后,H中的每个节点都可以以Dijkstra方式进行设置,因此算法的复杂性仅限于Dijkstra(其可以处理开关数量的100k上限)。

答案 1 :(得分:1)

正如我的评论建议:我认为您可以通过edge based graphan improvement或多或少是基于“增强型”节点的图表解决此问题。

详细说明:

  • 您的情况类似于道路网络中的转弯限制。如果您为每个(定向!)街道创建一个节点并根据允许的转弯连接这些节点,则可以对这些节点进行建模。
  • 因此,不仅要存储当前位置的位置,还要存储方向以及可能的其他“情况”。为了使180°转弯的相同位置与您当前的状态不同。

不是将你的'状态'(定向!)建模到图表中,你也可以为每个交叉点分配可能的结果 - 现在算法需要更加聪明,并且需要根据你的早期决定每个交叉点做什么国家(包括方向)。我认为,这是基于“增强型”节点的图形的主要思想,它应该减少内存密集度(在您的情况下并不重要)。

相关问题