问题 用Python解决图形问题


我有一种情况,我想用Python来解决这个问题,但不幸的是我对图表知之甚少。我找到了一个看起来非常适合这个相对简单的任务的库, networkx,但是我在做我想要的事情时遇到了问题,这应该是相当简单的。

我有一个节点列表,可以有不同的类型,以及两个“类”的邻居,向上和向下。任务是在两个目标节点之间找到路径,并考虑到一些约束:

  • 只能遍历特定类型的节点,即如果起始节点的类型为x,则路径中的任何节点都必须来自另一组路径y或z
  • 如果节点的类型为y,则只能传递一次
  • 如果节点具有类型z,则可以传递两次
  • 如果访问类型为z的节点,则出口必须来自不同类别的邻居,即如果从上向下访问,则出口必须从向下

所以,我尝试了一些实验,但正如我所说,我一直在努力。首先,我不确定这实际代表什么类型的图表?它不是方向性的,因为从节点1到节点2,或从节点2到节点1无关紧要( 在最后一个场景中,所以使事情变得复杂......)。这意味着我不能只创建一个简单的多向图,因为我必须考虑到这个约束。其次,我必须遍历这些节点,但指定只有特定类型的节点必须可用于路径。此外,如果最后一个场景发生,我必须记住入口和出口类/方向,这使它处于某种有针对性的状态。

这是一些示例模型代码:

import networkx as nx

G=nx.DiGraph()
G.add_node(1, type=1)
G.add_node(2, type=2)
G.add_node(3, type=3)
G.add_edge(1,2, side="up")
G.add_edge(1,3, side="up")
G.add_edge(2,1, side="down")
G.add_edge(2,3, side="down")
for path in nx.all_simple_paths(G,1,3):
    print path

输出相当不错,但我需要这些约束。那么,您是否有一些建议我如何实现这些,或者给我一些关于理解这类问题的指导,或者针对这个问题提出不同的方法或库?也许一个简单的基于字典的算法适合这种需要?

谢谢!


7740
2017-12-10 11:59


起源

很难理解你想要什么。你能给出一些问题的背景吗?不同类型的节点代表什么?你想要两个节点之间的所有路径,还是只需要最短的路径?它有多快? (“所有路径”可能会在打印输出时将其推至指数时间) - Andy Jones
让我们说节点代表城市或站点,某种具有特定“类型”的位置,表示其大小或一些其他限制因素。这些节点有三种类型。所有路径都应该在那里,而不仅仅是最短路径。速度是无关紧要的因素,但我想等待一个小时来解析一些节点:)我将用100个节点以下的非常小的数据来测试它,所以速度绝对不是问题。 - wont_compile
我并不完全理解这些限制。可以是类型的节点 X 只通过一次或任意多次?节点之间的连接是完全基于这三个类,还是需要关注其他结构? - Michael J. Barber
类型X节点只是一个起点和终点,除了这两个状态外,没有路径不能包含它们。之间的连接具有额外的“侧”,向上和向下,这与最终约束相关。除此之外,以及实际节点的类型,没有其他用于描述节点/连接的附加内容。关于节点y要求,节点只能出现一次,但路径中可以出现多个y节点。所以,两个y是可以的,但它们必须是不同的节点。类似于z,但同一节点可以传递两次。 - wont_compile


答案:


如果以不同方式构造图形,则可以对问题使用all_simple_paths()函数。简单路径是没有重复节点的路径。因此,对于您的约束,这里有一些构建图形的建议,以便您可以不修改地运行该算法。

  • 只能遍历特定类型的节点,即如果起始节点的类型为x,则路径中的任何节点都必须来自另一组路径y或z

给定起始节点n,在找到路径之前删除具有该类型的所有其他节点。

  • 如果节点的类型为y,则只能传递一次

这是简单路径的定义,因此它会自动满足。

  • 如果节点具有类型z,则可以传递两次

对于z类型的每个节点n,添加一个新节点n2,其边缘与指向n和n的节点相同。

  • 如果访问类型为z的节点,则出口必须来自不同类别的邻居,即如果从上向下访问,则出口必须从向下

如果边缘是按照你的建议定向的,那么如果你确保z的边都是相同的方向,那么这可以得到满足 - 例如为了向下和向下...


7
2017-12-18 00:54



我试图使用图形小工具解决这个问题,但问题是上/下边缘。如果您有向上和向下的向下,那么您将找不到任何使用下行路径到达的路径并使用向上路径离开。如果再次复制z节点,以便可以使用up / in和one具有up / out,则不能对其进行约束,以便每个z节点都传递两次。 - Andy Jones
我懂了。您可能必须修改simple_paths算法以跟踪在这种情况下访问每种类型节点的次数。 - Aric
每个节点z“可以通过两次”。它不会要求您完全遍历节点z两次。 - justhalf
嘿,这很有帮助,因为它获得了最多的奖励,我获得了赏金,这么棒的工作:)但是,我必须编写自己的解决方案,从你的答案中得到一些帮助,我会很快发布我的答案,并告诉我如何设法做到这一点。我完全避免使用网络x并做了一些“技巧”,这似乎是一个正确的解决方案。关于仅被访问过一次的类型x节点的一个小评论,有一些类型的“子集”,比如1-2-3,它们是第一种类型,但也有区别。 - wont_compile


我认为最好的方法是通过最多计算所有有效的长度路径 ķ 来源之间 小号 和其他每个节点,然后使用该信息计算最多长度的所有有效路径 K + 1。然后,您只需重复此操作,直到获得没有修改路径的固定点。

实际上,这意味着您应该在每个节点上设置路径列表。在每个步骤中,您将获取每个节点 ü 反过来看看在上一步中终止于某个邻居的路径 V 的 ü。如果这些路径中的任何一个可以扩展为新的,不同的路径 ü,扩展它并将其添加到 ü的清单。

如果执行步骤时没有找到新路径,那就是终止状态。然后,您可以检查目标节点上的路径列表 Ť

伪代码(以非常松散的C#形式):

var paths = graph.nodes.ToDictionary(node => node, node => new List<List<node>>())
paths[S].Add(new List<node> {S}) // The trivial path that'll start us off.


bool notAFixedPoint = true;

while (notAFixedPoint)
{
    notAFixedPoint = false // Assume we're not gonna find any new paths.

    foreach (var node in graph)
    {
        var pathsToNode = paths[node]

        foreach (var neighbour in node.Neighbours)
        {
            var pathsToNeighbour = paths[neighbour]

            // ExtendPaths is where all the logic about how to recognise a valid path goes.
            var newPathsToNode = ExtendPaths(pathsToNeighbour, node)

            // The use of "Except" here is for expository purposes. It wouldn't actually work,
            // because collections in most languages are compared by reference rather than by value.
            if (newPathsToNode.Except(pathsToNode).IsNotEmpty())
            {
                // We've found some new paths, so we can't terminate yet.
                notAFixedPoint = true 

                pathsToNode.AddMany(newPathsToNode)
            }
        }
    }
}

return paths[T] 

5
2017-12-13 09:51



因此,对于第一部分,我将创建从源S到其邻居的路径,并且具有k的长度。然后,在长度k + 1,这将添加来自与连接到源的节点相邻的节点的路径,并以这种方式给出从源到这些节点的路径。并迭代这个直到我得到结果中的目标节点?我怎么知道如何停止,因为可能有多条路径。你可以用一些伪代码指导我,我真的没有很多编写这种东西的经验。谢谢! - wont_compile
编辑更有意义吗? - Andy Jones
感谢您的投入和您的见解,我花了更多的时间来处理这个并从不同的角度来看解决方案,除了这个伪代码之外的另一部分是约束,这是这个问题的“关键”。 - wont_compile


对我来说,这看起来像是一个优化问题 - 查找“旅行推销员”,找到一个与您想要做的有点接近的经典示例。

我有幸使用“模拟退火”来解决优化问题,但您也可以看看“遗传算法”。


-1
2017-12-17 22:33



不幸的是,我认为遗传算法根本不适合这个问题。突变和交叉可能很少会产生实际上是解决方案的东西。 - Hannes Ovrén
我同意,我必须对此进行一些不同的编码,我希望它能够,我会尽快发布我的答案以供进一步参考。 - wont_compile