【问题标题】:How do find the longest path in a cyclic Graph between two nodes?如何在循环图中找到两个节点之间的最长路径?
【发布时间】:2010-04-20 22:15:46
【问题描述】:

我已经解决了here 发布的大部分问题,除了最长的路径之一。我已经阅读了关于最长路径的 Wikipedia 文章,如果图表是非循环的,这似乎很容易出现问题,而我的不是。

那我该如何解决这个问题呢?蛮力,通过检查所有可能的路径?我该如何开始呢?

我知道在大约 18000 的图表上会花费很多。但无论如何我只想开发它,因为它是项目所必需的,我将对其进行测试并以较小比例的图表展示给讲师,其中执行时间仅为一两秒。

至少我完成了所有要求的任务,并且我有一个运行的概念证明它可以工作,但在循环图上没有更好的方法。但我不知道从哪里开始检查所有这些路径...

【问题讨论】:

  • 在循环图上最长的路径将是无限长的,不是吗?你将只是转来转去,转来转去......
  • 即使我标记了访问过的节点以便我不再访问它们?这是我仍然无法理解的原因。在我看来,它应该和 Dijkstra 算法一样,只是“逆向”。如下所示,但我无法使其工作。算法完成,但结果似乎不正确。

标签: c graph longest-path cyclic-graph


【解决方案1】:

解决方案是暴力破解。你可以做一些优化来加速它,有些是微不足道的,有些是非常复杂的。我怀疑你能否让它在台式计算机上为 18 000 个节点足够快地工作,即使你能做到,我也不知道怎么做。然而,暴力破解的工作原理如下。

注意:如果您对确切答案感兴趣,Dijkstra 和任何其他最短路径算法都不适用于此问题。

Start at a root node *root*
Let D[i] = longest path from node *root* to node i. D[*root*] = 0, and the others are also 0.

void getLongestPath(node, currSum)
{
    if node is visited
        return;
    mark node as visited;

    if D[node] < currSum
        D[node] = currSum;

    for each child i of node do
        getLongestPath(i, currSum + EdgeWeight(i, node));

    mark node as not visited;
}

让我们在这个图表上手动运行它:1 - 2 (4), 1 - 3 (100), 2 - 3 (5), 3 - 5 (200), 3 - 4 (7), 4 - 5 (1000)

Let the root be 1. We call getLongestPath(1, 0);
2 is marked as visited and getLongestPath(2, 4); is called
D[2] = 0 < currSum = 4 so D[2] = 4.
3 is marked as visited and getLongestPath(3, 4 + 5); is called
D[3] = 0 < currSum = 9 so D[3] = 9.
4 is marked as visited and getLongestPath(4, 9 + 7); is called
D[4] = 0 < currSum = 16 so D[4] = 16.
5 is marked as visited and getLongestPath(5, 16 + 1000); is called
D[5] = 0 < currSum = 1016 so D[5] = 1016.
getLongestPath(3, 1016 + 200); is called, but node 3 is marked as visited, so nothing happens.
Node 5 has no more child nodes, so the function marks 5 as not visited and backtracks to 4. The backtracking will happen until node 1 is hit, which will end up setting D[3] = 100 and updating more nodes.

以下是它的迭代外观(未经测试,只是一个基本想法):

Let st be a stack, the rest remains unchanged;
void getLongestPath(root)
{
    st.push(pair(root, 0));

    while st is not empty
    {
        topStack = st.top();
        if topStack.node is visited
            goto end;
        mark topStack.node as visited;

        if D[topStack.node] < topStack.sum
            D[topStack.node = topStack.sum;

        if topStack.node has a remaining child (*)
            st.push(pair(nextchild of topStack.node, topStack.sum + edge cost of topStack.node - nextchild)) 

        end:
        mark topStack.node as not visited
        st.pop();
    }
}

(*) - 这有点问题 - 你必须为每个节点保留一个指向下一个子节点的指针,因为它可以在 while 循环的不同迭代之间改变甚至重置自身(指针在您将topStack.node 节点从堆栈中弹出,因此请确保将其重置)。这在链表上最容易实现,但是您应该使用int[] 列表或vector&lt;int&gt; 列表,以便能够存储指针并进行随机访问,因为您将需要它。例如,您可以保留 next[i] = next child of node i in its adjacency list 并相应地更新它。您可能会遇到一些边缘情况,并且可能需要不同的end: 情况:正常情况和访问已访问节点时发生的情况,在这种情况下不需要重置指针。也许在你决定将某些东西压入堆栈之前移动访问的条件以避免这种情况。

明白我为什么说你不应该打扰吗? :)

【讨论】:

  • 我无法对此发表评论,因为我不得不离开,我只是来这里看看是否有答案。但是,它可以在没有递归的情况下以简单的方式完成,还是让事情变得复杂?几个小时后我下课回来后会花更多时间查看你的帖子。
  • 递归只是意味着在内存中维护一个隐式堆栈,其中函数参数和局部变量之类的东西被推送到每个函数调用。您可以自己维护该堆栈,从而避免递归,但我认为它只会使事情复杂化。递归不是这里的瓶颈。您应该专注于启发式优化(例如,如果D[node] &gt;= currSum,我认为您可以返回)。这类似于旅行推销员问题,因此您可能需要在 Google 上搜索并查看其他人的想法。
  • 另外,考虑使用近似算法。您还必须返回最佳答案,还是足够接近的东西也很好?考虑研究贪婪逼近算法和遗传算法。如果您让遗传算法运行足够长的时间,它们也可以为您提供最佳解决方案。
  • 在这一点上,我真的不关心优化,我真的没有时间去打扰。我给导师发了电子邮件,他明白这需要一段时间,这是展示项目时要讨论的事情之一。我谈到递归,不是因为性能,而是因为我更喜欢这种方式,仅此而已。否则我将不得不通过函数参数传递相当多的变量,我倾向于避免这种情况,因为它会使事情复杂化(至少对我而言)。雨停了,我看看能不能去上课:X
  • 1) 糟糕,抱歉 :)。你是对的,最初不要标记根。 2)您无法完全停止它,因为您不知道何时完成了最终更新。只需从节点 X 开始并打印 D[Y]。 Dijkstra 的工作原理相同,它也计算从起始节点到至少一些其他节点的距离。 3) 保留T[x] = the last node that updated the distance to x。例如,将father 参数添加到函数。然后,当D[node] &lt; currSum 触发时设置T[node] = father。 4) 是的,您应该实现自己的堆栈。它变得非常混乱,你真的应该只使用递归 IMO。
猜你喜欢
  • 2011-04-26
  • 2011-03-08
  • 2015-04-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多