【问题标题】:finding longest path in an adjacency list在邻接列表中找到最长的路径
【发布时间】:2013-04-30 00:07:51
【问题描述】:

我为给定的图创建了一个邻接列表,其中包含节点和加权边。我试图弄清楚在图中找到最长路径的最佳方法是什么。我有一个拓扑排序方法,我听说它很有用,但我不确定如何实现它来找到最长的路径。那么有没有办法使用拓扑排序来实现这一点,或者有没有更有效的方法?

这是我的调整列表的示例(括号中的值是到达箭头(cost)to get to -> node之后的节点的成本:

Node 0 (4)->1(9)->2
Node 1 (10)->3
Node 2 (8)->3
Node 3
Node 4 (3)->8(3)->7
Node 5 (2)->8(4)->7(2)->0
Node 6 (2)->7(1)->0
Node 7 (5)->9(6)->1(4)->2
Node 8 (6)->9(5)->1
Node 9 (7)->3
Node 10 (12)->4(11)->5(1)->6

【问题讨论】:

    标签: c sorting traversal adjacency-list


    【解决方案1】:

    Bryan 已经在上面回答了您的问题,但我认为我可以更深入地回答。

    首先,正如他所指出的,这个问题只有在没有循环的情况下才能轻松解决。如果存在循环,您会遇到路径无限长的情况。在这种情况下,您可以将最长路径定义为没有重复节点的任何路径。不幸的是,这个问题可以证明是 NP-Hard。因此,相反,我们将专注于看起来您实际上需要解决的问题(因为您提到了拓扑排序)——有向无环图 (DAG) 中的最长路径。我们还将假设我们有两个节点s 和t,它们是我们的开始和结束节点。除非您可以对图表做出某些假设,否则问题会更难看。如果您理解下面的文字,并且图中的此类假设是正确的,那么也许您可以删除 s 和 t 限制(否则,您必须在图中的每一对顶点上运行它!慢...)

    算法的第一步是对顶点进行拓扑排序。直觉上这是有道理的。假设您从左到右对它们进行排序(即最左边的节点将没有传入边)。从s 到t 的最长路径通常会从左侧开始并在右侧结束。这条路也不可能向左走。这为您提供了生成最长路径的顺序 - 从左侧开始并向右移动。

    下一步是从左到右依次为每个节点定义最长的路径。对于没有传入边的任何节点,该节点到的最长路径为 0(根据定义,这是真的)。对于具有传入边的任何节点,递归地定义该节点的最长路径到为所有传入边上的最大值+到达“传入”邻居的最长路径(请注意,这个数字可能是负数,例如,如果所有传入边都是负的!)。直觉上这是有道理的,但证明也很简单:

    假设我们的算法声称到某个节点v 的最长路径是d,但实际最长路径是某个d' > d。选择“最少”这样的节点v(我们使用拓扑排序定义的排序。换句话说,我们选择算法失败的“最左边”节点。这很重要,因此我们可以假设我们的算法已经正确确定了v“左侧”任何节点的最长路径)。将假设的最长路径的长度定义为d' = d_1 + e,其中d_1 是到节点v_prev 的假设路径的长度,边缘e 到v(注意草率的命名。边缘@987654337 @ 也有权重e)。我们可以这样定义它,因为任何到v 的路径都必须经过它的一个邻居,该邻居有一个通往v 的边缘(因为如果不通过一些通往它的边缘到达那里,你就无法到达v )。那么d_1 必须是到v_prev 的最长路径(否则,矛盾。有一条更长的路径与我们选择v 作为“最少”这样的节点相矛盾!)我们的算法将选择包含@987654345 的路径@ 随意。

    要生成实际路径,您可以确定使用了哪条边。假设您已经重建了到达某个顶点v 的路径,该顶点具有最长的路径长度d。然后遍历所有传入的顶点,找到路径长度最长的那个d' = d - e,其中e 是进入v 的边的权重。您也可以在通过算法时跟踪节点的父节点。也就是说,当您找到到v 的最长路径时,将其父节点设置为选择的任何相邻节点。您可以使用简单的矛盾来说明为什么任何一种方法都会生成最长的路径。

    最后是一些伪代码(对不起,它基本上是用 C# 编写的。在没有自定义类的情况下用 C 编写代码会更麻烦,而且我有一段时间没有编写 C 代码了)。

    public List<Nodes> FindLongestPath(Graph graph, Node start, Node end)
    {
        var longestPathLengths = Dictionary<Node, int>;
    
        var orderedNodes = graph.Nodes.TopologicallySort();
        // Remove any nodes that are topologically less than start. 
        // They cannot be in a path from start to end by definition
        while (orderedNodes.Pop() != start);
        // Push it back onto the top of the stack
        orderedNodes.Push(start);
    
        // Do algorithm until we process the end node
        while (1)
        {
            var node = orderedNodes.Pop();
            if (node.IncomingEdges.Count() == 0)
            {
                longestPathLengths.Add(node, 0);
            }
            else
            {
                var longestPathLength = Int.Min;
                foreach (var incomingEdge in node.IncomingEdges)
                {
                    var currPathLength = longestPaths[incomingEdge.Parent] +               
                                         incomingEdge.Weight);
                    if (currPathlength > longestPathLength)
                    {
                        longestPath = currPathLength;
                    }
                }
    
                longestPathLengths.Add(node, longestPath);
            }
    
            if (node == end)
            {
                break;
            }
        }
    
        // Reconstruct path. Go backwards until we hit start
        var node = end;
        var longestPath = new List<Node>();
        while (node != start)
        {
            foreach (var incomingEdge in node.IncomingEdges)
            {
                if (longestPathLengths[incomingEdge.Parent] == 
                        longestPathLengths[node] - incomingEdge.Weight)
                {
                    longestPath.Prepend(incomingEdge.Parent);
                    node = incomingEdge.Parent;
                    break;
                }
            }
        }
    
        return longestPath;
    }
    

    请注意,这种实现并不是特别有效,但希望它很清楚!您可以通过许多小方式进行优化,这些方式在您考虑代码/实现时应该是显而易见的。一般来说,如果你在内存中存储更多的东西,它会运行得更快。您构建Graph 的方式也很重要。例如,您的节点似乎没有 IncomingEdges 属性。但是没有它,为每个节点找到传入的边是一件很痛苦的事情(而且不是高性能的!)。在我看来,图算法在概念上不同于字符串和数组算法,因为实现非常重要!如果您阅读有关图形算法的 wiki 条目,您会发现它们通常会根据不同的实现(具有不同的数据结构)提供三到四种不同的运行时。如果您关心速度,请记住这一点

    【讨论】:

      【解决方案2】:

      假设您的图没有环,否则最长路径将成为一个模糊的概念,您确实可以进行拓扑排序。现在您可以进行这种拓扑排序,并通过查看其所有前任节点来计算每个节点与源节点的最长距离,并将连接它们的边的权重添加到它们的距离中。然后选择为您提供该节点最长距离的前任。拓扑排序保证所有前辈的距离都已经正确确定。

      如果除了最长路径的长度,你还想要路径本身。然后你从给出最长长度的节点开始,查看它的所有前辈,找到导致这个长度的那个。然后重复这个过程,直到找到图的源节点。

      【讨论】:

        猜你喜欢
        • 2019-11-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-06-01
        • 2019-04-10
        • 1970-01-01
        相关资源
        最近更新 更多