c# – 递归到Tail递归

我正在编写一个遍历以找到道路中最长的路径.这段代码的神奇部分是segment.Next指的是LINQ,它具有应用于它的特定逻辑,就像不重新访问已访问过的节点一样.因此,不要指出travsel中的缺陷超出范围.

我想要做的是减少堆栈上的调用次数,因为有时路径可能是5000长.我知道我必须使这个递归调用尾递归.

public static IEnumerable<Segment> FindLongestPath(Segment segment)
{
    var rv = new List<Segment> {segment};
    var longestPathLength = 0;
    var longestNextPaths = Enumerable.Empty<Segment>();

    foreach (var n in segment.Next)
    {
        var paths = FindLongestPath(n);
        var length = paths.Sum(p => p.LengthMeters);
        if (length > longestPathLength)
        {
            longestPathLength = length;
            longestNextPaths = paths;
        }
    }

    rv.AddRange(longestNextPaths);

    return rv;
}

如何使这个递归调用成为尾递归?我知道我可能需要维护IEnumerable< Segment>因为我旅行,但我只是没有把头包裹起来.

解决方法:

spender的答案是在没有递归的情况下解决这个问题的实用方法:使用显式堆栈或队列作为帮助器.

原始问题和消费者,在评论中,分别想知道如何在尾递归样式和延续传递样式中执行此算法. (CPS是一种编程风格,其中每个调用都是尾调用.)

为了让您了解该算法的CPS版本的外观,让我(1)大大简化问题,(2)用ML编写解决方案,而不是C#.简化的问题是:

>函数children接受一个节点并生成一堆子节点.
>功能成本给出了遍历单个节点的成本.
>给出的问题是找到最大成本路径的成本.

首先,ML中直接的非CPS解决方案:

let rec maximum_path_cost node =
  let rec aux nodes max =
    match nodes with
    | [] -> max
    | head :: tail -> 
       let c = maximum_path_cost head in
       let new_max = if c > max then c else max in
       aux tail new_max
  in
  (cost node) + (aux (children node) 0)

简而言之:我们使用递归辅助函数模拟一个循环,累积到目前为止看到的最大值.循环条件是“列表是空的吗?”如果是,则结果是迄今为止看到的最大值;如果没有,那么我们计算当前项目(列表的头部)的成本,将其与最大值进行比较,并在尾部运行循环.

请注意,aux是尾递归,但maximum_path_cost不是.

在继续传递样式中,maximum_path_cost采用延续 – 在这种情况下,是一个接受int的函数 – 并且需要使用其结果调用该函数,而不是返回.我们将使aux做同样的事情.

为简单起见,我们不会将成本和子项转换为CPS.

let rec maximum_path_cost node continuation =
  let rec aux nodes max aux_continuation =
    match nodes with
    | [] -> aux_continuation max
    | head :: tail ->
       let mpcc c = 
         let new_max = if c > max then c else max in
         aux tail new_max aux_continuation
       in
       maximum_path_cost head mpcc
  in
  let ac result =
    continuation ((cost node) + result) 
  in
    aux (children node) 0 ac

我知道很难将你的大脑包裹起来,但如果你仔细阅读它,它应该是有意义的.我们要做的第一件事是与孩子们一起调用aux,当前最大值为零;什么是第一次调用aux的延续?将其结果添加到头部的开销中,并将其传递给maximum_path_cost的延续.我们什么时候这样做?当我们运行整个子节点列表并且没有剩下的时候.

将其转换为C#并使C#保证尾递归仍然是一个练习.

上一篇:python – 金字塔遍历正在努力


下一篇:javascript – jQuery – 关于“nextWhile”traversion的建议?