繁体   English   中英

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

[英]Finding the minimum cycle path in a dynamically directed graph

最近,我在今年早些时候遇到了Spotify的黑客挑战中的一个有趣的问题(编辑:问题A)该问题涉及确定火车卡车路口的转换以将火车送回到起点。 火车必须以与左方向相同的方向到达,火车永远不能在铁轨上倒退。

据我了解,该问题可以建模为无向(?)图,其中我们必须从某个顶点中找到最短的周期,或者检测到不存在这样的周期。 但是,有趣的是,对于顶点v,与v相邻的顶点取决于通向v的路径,因此在某种意义上,可以将图形视为有向的,尽管该方向与路径有关。

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

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

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

更新:我遵循@Karussell在评论中建议的方法。

这是我在github上的解决方案。

诀窍是使用基于边缘的图而非传统的基于顶点的图对情况建模。 竞赛中提供的输入文件已经方便地根据边进行了指定,因此可以轻松地使用该文件来构建基于边的图形。

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

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

至此,我们的图已构建完毕。 为了找到最短路径,我仅使用BFS遍历图形。 我们保留根的未标记状态,并从排队根的邻接处开始。 如果找到目标交界处为“ 1A”(索引0)的道路,那么我们发现到起点的周期最短。 一旦我们使用每个Road的父级属性重建路径,便可以轻松地根据问题中的需要设置开关。

感谢Karussell提出了这种方法。 如果您想以简短的解释在评论表中添加您的评论,我将接受。 还要感谢@Origin。 我必须承认,我没有完全遵循您的回答的逻辑,但这当然不是说它是不正确的。 如果有人使用您的解决方案解决了这个问题,我将非常有兴趣看到它。

一种可能的方法:首先,用某种图形对所有连接建模(图形G)。 然后构造另一个图,在其中我们将找到周期(图H)。 对于G中的每个节点A,我们将向图H添加一个节点。每个A节点还具有2个传出边(到图G中的B和C节点)。 在H中,这些边缘将转到G中遇到的下一个A节点。例如,与ID为3的交换机的A节点相对应的H中的A节点将具有到节点9和6中的节点6的输出边缘。 H.每条边的权重是该路由上经过的交换机的数量(包括起始交换机)。

这将产生一个图,我们可以在其中生长前向最短路径树。 如果我们再次到达起点,那么循环将完成。

关键是,如果开关沿A->方向移动,则它只是一个决定点。 不必对反向建模,因为这只会使搜索复杂化。

编辑:更多澄清

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

在H图中,每个开关都有一个节点。 每个节点将具有2个传出边,分别对应一个可采用的2条路径(B和C方向)。 H中的边将对应于原始图中2 A个节点之间的整个路径。 对于问题描述中的示例,我们得到以下信息:

交换机1对应的节点:

  • 1到节点2的出站链接,权重2,对应于离开开关1时的C方向。权重为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开始,有2个可能的方向,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中)。 然后,可以用Dijkstra的方式解决H中的每个节点,因此算法的复杂度仅限于Dijkstra的复杂度(Dijkstra可以处理100k的开关上限)。

正如我的评论所建议的那样:我认为您可以通过基于边缘的图或通过某种或多或少的基于节点的“增强”图的改进来解决此问题。

细节:

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

除了可以将“状态”(定向!)建模到图中之外,您还可以为每个结点分配可能的结果-现在,算法需要更聪明,并且需要根据您先前的状态(包括方向)。 我认为,这是基于“增强型”节点图的主要思想,该图应减少内存密集性(在您的情况下不那么重要)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM