【问题标题】:Finding the heaviest length-constrained path in a weighted Binary Tree在加权二叉树中找到最重的长度约束路径
【发布时间】:2011-02-21 02:20:57
【问题描述】:

更新

我制定了一个我认为在 O(n*k) 运行时间内运行的算法。下面是伪代码:

routine heaviestKPath( T, k )

    // create 2D matrix with n rows and k columns with each element = -∞
    // we make it size k+1 because the 0th column must be all 0s for a later 
    // function to work properly and simplicity in our algorithm
    matrix = new array[ T.getVertexCount() ][ k + 1 ] (-∞);

    // set all elements in the first column of this matrix = 0
    matrix[ n ][ 0 ] = 0;

    // fill our matrix by traversing the tree
    traverseToFillMatrix( T.root, k );

    // consider a path that would arc over a node
    globalMaxWeight = -∞;
    findArcs( T.root, k );

    return globalMaxWeight
end routine


// node = the current node; k = the path length; node.lc = node’s left child; 
// node.rc = node’s right child; node.idx = node’s index (row) in the matrix; 
// node.lc.wt/node.rc.wt = weight of the edge to left/right child;
routine traverseToFillMatrix( node, k )

   if (node == null) return;

   traverseToFillMatrix(node.lc, k ); // recurse left
   traverseToFillMatrix(node.rc, k ); // recurse right

   // in the case that a left/right child doesn’t exist, or both,
   // let’s assume the code is smart enough to handle these cases
   matrix[ node.idx ][ 1 ] = max( node.lc.wt, node.rc.wt );


   for i = 2 to k {
       // max returns the heavier of the 2 paths
       matrix[node.idx][i] = max( matrix[node.lc.idx][i-1] + node.lc.wt, 
                                  matrix[node.rc.idx][i-1] + node.rc.wt);
   }

end routine



// node = the current node, k = the path length
routine findArcs( node, k ) 

    if (node == null) return;

    nodeMax = matrix[node.idx][k];
    longPath = path[node.idx][k];

    i = 1;
    j = k-1;
    while ( i+j == k AND i < k ) {

        left = node.lc.wt + matrix[node.lc.idx][i-1];
        right = node.rc.wt + matrix[node.rc.idx][j-1];

        if ( left + right > nodeMax ) {
            nodeMax = left + right;
        }
        i++; j--;
    }

    // if this node’s max weight is larger than the global max weight, update
    if ( globalMaxWeight < nodeMax ) {
        globalMaxWeight = nodeMax;
    }

    findArcs( node.lc, k ); // recurse left
    findArcs( node.rc, k ); // recurse right

end routine

让我知道你的想法。欢迎反馈。


认为提出了两个简单的算法,可以在加权二叉树中找到最重的长度约束路径。首先,算法描述如下:给定一个带权边的n-顶点二叉树,取值k,求长度为k的最重路径。

对于这两种算法,我都需要一个对所有顶点的引用,所以我只需简单地遍历树以引用所有顶点,每个顶点都引用它的左、右和父节点树中的节点。

算法 1 对于这个算法,我基本上计划从树中的每个节点运行 DFS,同时考虑到固定的路径长度。此外,由于我正在寻找的路径有可能从左子树到根到右子树,我将不得不在每个节点上考虑 3 个选择。但这会导致 O(n*3^k) 算法,我不喜欢这样。

算法 2 我本质上是在考虑使用Dijkstra's Algorithm 的修改版本来考虑固定的路径长度。由于我正在寻找最重的并且 Dijkstra 算法找到最轻的,因此我计划在开始遍历之前否定所有边缘权重。实际上......这没有任何意义,因为我必须在每个节点上运行 Dijkstra,这似乎并不比上述算法效率高得多。

所以我想我的主要问题有几个。首先,我上面描述的算法是否解决了手头的问题?我不完全确定 Dijkstra 的版本是否会起作用,因为 Dijkstra 的版本适用于正边缘值。

现在,我确信存在更聪明/更有效的算法……什么是更好的算法?我读过“使用脊椎分解来有效地解决树木的长度受限的最重路径问题”,但这真的很复杂,我根本不明白。是否有其他算法可以解决这个问题,可能不如脊椎分解有效但更容易理解?

【问题讨论】:

  • 对于 Algo1:请注意 Dijkstra 不适用于负权重。
  • 对...但我认为这是关于负循环,即是否同时存在 + 和 - 边缘?为什么它不适用于所有负边?
  • 所有负边也可能有负循环......但是,在二叉树的情况下,这可能是不可能的。 Dijkstra 并不适合二叉树。 Dijkstra 回答了从源到任何目的地的最短路径是什么的问题,在二叉树中,任何 2 个顶点之间实际上只有 1 条路径,这对于你想要的来说真的太复杂了。除非,我误解了你所说的二叉树的意思。
  • 我认为您的 DFS 想法可行。您从树中的每个节点开始,向下递归 N 层,保持总权重的运行计数。达到第 N 级后,与整体最佳路径进行比较,并在必要时对其进行更新。
  • 您的最新算法看起来不错。 :) 一些挑剔:第 9 行似乎并没有像您的评论所建议的那样将整个列设置为 0,只是第 n 个元素。这将修复fillArcs() 中的一个错误,您可以在第一次循环迭代时访问列i-1,并且还可以让您停止traverseToFillMatrix() 中的特殊情况列0——只需在for i = 1 to k 中进行循环。在findArcs() 中,longPath = path[node.idx][k]; 行可以删除,循环条件i+j == k AND i &lt; k 应更改为i &lt;= k(允许纯右侧路径,i+j == k 将始终为真)。

标签: algorithm theory binary-tree


【解决方案1】:

您可以从每个节点向下使用 DFS,该节点在 k 边缘之后停止以搜索路径,但请注意,这将在每个节点上执行 2^k 次工作,总共 O(n* 2^k) 有效,因为从起始节点往下走的每一层的路径数量都会翻倍。

正如 DasBoot 在评论中所说,在这里使用 Dijkstra 算法没有任何优势,因为它的聪明之处在于在可能有多条路线时选择最短(或最长)的方法来获得 2 点之间的距离。一棵树总是只有一种方式。

我有一个动态规划算法,需要 O(nk) 时间。以下是一些提示:

  • 如果您选择某个叶顶点作为根 r 并将所有其他顶点向下指向远离根的位置,请注意此定向树中的每条路径都有一个 最高节点 em> -- 即离r最近的唯一节点。
  • 您可以通过遍历每个节点 v 并计算最重的长度 -k 路径来计算总体上最重的长度 -k 路径,其最高节点为v,最终取所有节点的最大值。
  • 最高节点为 v 的 length-k 路径必须有一个向一个子节点下降的 length-i 路径和一个 length-( k-i) 路径下降到另一个。

这应该足以让您朝着正确的方向思考;如果您需要进一步的帮助,请告诉我。

【讨论】:

  • 有趣...我会认真考虑一下并回复您。然而,我想澄清一下,在我使用 DFS 的愿景中,我计划在每个节点上有 3 个选择,即左孩子、右孩子和父母。所以情况更糟:)
  • 嘿,我回来解决这个问题了。我无法完全想象您将所有其他顶点向下引导的意思,远离根...哪个根?原来的还是我摘的叶子?你介意提供一个插图吗?
  • 对于每个节点,您可以以该节点为根存储长度为“i”的最佳路径,即 best[i]。如果节点 v 有 2 个子节点 v->left 和 v->right,则 best[v][i] = max(best[v->left][i-1]+weight[v][v->left] , 最好[v->right][i-1]+weight[v][v->right])。找到 best[n][k] 应该是 O(nk)。一旦你有了它,你必须遍历树中的所有节点 v,并在范围 (0,k) 中找到 i:max(best[v->left][i-1]+best[v->right] [k-i-1]+weight[v][v->left]+weight[v][v->right])。一旦你得到每个 v 的最大值,然后在树中的所有 v 上取最大值,所以这应该是 O(nk)。
  • @DasBoot:点对点,但这是一个家庭作业问题,所以最好给出提示...... :)
  • @Hristo:如果您在通常的 CS 意义上使用术语 tree,那么您可能已经假设它在顶部有一个唯一的根顶点并且所有边都是有向的远离它,在这种情况下,您可以无视我在那里所说的。但是在图论中,术语 tree 并不意味着边的方向性,或唯一的“根”节点的存在——它只是连接它们的顶点和边的集合,因此只有一条路径边数连接任意 2 个顶点。这可以通过选择任何顶点作为根并将所有边“远离”它变成“CS树”。
【解决方案2】:

这是我的解决方案。欢迎反馈。

让我们将二叉树视为一个有向图,边从父节点到子节点。让我们为每个顶点 v 定义两个概念:

a) 弧:是一条有向路径,即从顶点v开始,路径中的所有顶点都是起始顶点v的子节点。

b) 子路径:它是包含 v 的有向或无向路径,也就是说,它可以在任何地方开始,在任何地方结束,并从 v 的子节点到 v,然后对它的另一个子节点说.弧集是子路径集的子集。

我们还定义了一个函数HeaviestArc(v,j),它给出了顶点j的最重的弧,在左侧或右侧,长度为j,从v开始。我们还定义了LeftHeaviest(v,j ),RightHeaviest(v,j) 分别为长度为 j 的最重的左右弧。

鉴于此,我们可以根据其子节点为每个顶点 v 定义以下重复:

LeftHeaviest(v,j) = weight(LeftEdge(v)) + HeaviestArc(LeftChild(v)),j-1);
RightHeaviest(v,j) = weight(RightEdge(v)) + HeaviestArc(RightChild(v)),j-1);
HeaviestArc(v,j) = max(LeftHeaviest(v,j),RightHeaviest(v,j));

这里的 j 这里从 1 到 k,HeaviestArc(v,0)=LeftHeaviest(v,0),RightHeaviest(v,0)=0。对于叶节点,HeaviestArc(v,0) = 0,对于所有其他 j,HeaviestArc(v,j)=-inf(我需要更彻底地考虑极端情况)。

然后HeaviestChildPath(v),包含v的最重子路径可以计算为:

HeaviestChildPath(v) = max{ for j = 0 to k LeftHeaviest(j) + RightHeaviest(k-j)}

最重的路径应该是所有子路径中最重的。

算法的估计运行时间应该是 O(kn) 阶。

【讨论】:

  • 我觉得我的算法和你的差不多。它在上面的更新中。让我知道你的想法。
  • 重复出现看起来不错。不过,您的定义存在一些问题:(a) 最好写成“弧线:从顶点 v 开始的有向路径,其中每个顶点都是前一个顶点的子节点”(“子节点”表示“直接子节点”,甚至如果您在当前定义中将“child”替换为“descendant”,则仍然存在一些歧义)。 “弧”也是一个令人困惑的术语选择,因为它具有“有向边”的标准图论含义。同样对于(b),“包含 v 的路径”应该是“最高点为 v 的路径”,因为您(正确地)不考虑包含 v 但在其上方延伸的路径。
【解决方案3】:
def traverse(node, running_weight, level):
  if level == 0: 
    if max_weight < running_weight:
        max_weight = running_weight
    return
  traverse(node->left,running_weight+node.weight,level-1)
  traverse(node->right,running_weight+node.weight,level-1)
  traverse(node->parent,running_weight+node.weight,level-1)


max_weight = 0
for node in tree:
  traverse(node,0,N)

【讨论】:

  • 这只考虑“直下”的路径——但您也可以在顶部有一个“折叠”的路径(即路径从某个节点 v 开始并通过两个孩子下降)。 -1 现在。
  • 是的......路径可以从左子树到根到右子树。
  • 好的。我错过了关于能够倒退的问题陈述中的这一点。修复了源代码以允许访问父项和子项。
  • 实际上我认为您更新的算法仍然是错误的,但方式不同 - 现在路径可以在节点与其左子节点(例如)之间来回反弹任意次数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-11
  • 2022-01-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-02
相关资源
最近更新 更多