将此方法从递归转换为迭代

时间:2012-03-07 10:56:10

标签: c# .net algorithm recursion iteration

我有以下递归方法,但我得到的是StackOverflow,因为列表太长了。请有经验的人将这段代码转换为迭代?

    private List<Node> FindWayFrom(
        Node srcNode,
        Node dstNode,
        List<Node> way,
        List<Node> visitedNodes)
    {
        if (visitedNodes.Contains(srcNode))
            return null;

        visitedNodes.Add(srcNode);
        way.Add(srcNode);

        IList connectedNodes = GetConnectedNodes(srcNode);

        if (connectedNodes == null || connectedNodes.Count == 0)
            return null;

        foreach (Node node in connectedNodes)
        {
            if (node == dstNode) return way;
            List<Node> result = FindWayFrom(node, dstNode, way, visitedNodes);

            if (result != null)
                return result;

            //It is not the correct way. Remove current changeset.
            way.Remove(node);
        }
        return null;
    }

4 个答案:

答案 0 :(得分:2)

这是一个快速尝试实现这一点:

public static class Router
{
  private class Frame
  {
    public Frame(Node node)
    {
      Node = node;
      NextChild = 0;
    }

    internal Node Node { get; private set; }
    internal int NextChild { get; set; }
  }

  /// <summary>
  ///  Finds a (but not necessarily the shortest) route from <paramref name="source" /> 
  ///  to <paramref name="destination" />.
  /// </summary>
  /// <param name="source"> Source node </param>
  /// <param name="destination"> Destination node </param>
  /// <returns> A list of nodes that represents the path from 
  ///  <paramref name="source" /> to <paramref name="destination" /> , including both 
  ///  <paramref name="source" /> and <paramref name="destination" /> . If no such path 
  ///  exists, <c>null</c> is returned. 
  /// </returns>
  public static IList<Node> FindFirstRoute(Node source, Node destination)
  {
    var visited = new HashSet<Node>();
    var path = new Stack<Frame>();
    path.Push(new Frame(source));
    var frame = path.Peek();

    while (frame != null)
    {
      if (frame.Node == destination)
      {
        return path.Select(x => x.Node).Reverse().ToList();
      }

      if (!visited.Add(frame.Node) || !DescendToNextChild(path, out frame))
      {
        frame = Backtrack(path);
      }
    }

    return null;
  }

  /// <summary>
  ///   Attempts to move to the next child of the node on top of the stack.
  /// </summary>
  /// <param name="path"> Current path </param>
  /// <param name="nextFrame"> Receives the new top frame in the path. If all children 
  ///  have already been explored, <paramref name="nextFrame" /> is set to <c>null</c> 
  /// </param>
  /// <returns> <c>true</c> if descending was successful, that is, if the current top 
  /// frame has any unexplored children left; otherwise, <c>false</c>. 
  /// </returns>
  private static bool DescendToNextChild(Stack<Frame> path, out Frame nextFrame)
  {
    var topFrame = path.Peek();
    var children = topFrame.Node.Children;
    if (children != null && children.Length > topFrame.NextChild)
    {
      var child = children[topFrame.NextChild++];
      path.Push(nextFrame = new Frame(child));
      return true;
    }
    nextFrame = null;
    return false;
  }

  /// <summary>
  ///   Backtracks from the path until a frame is found where there is an unexplored 
  ///   child left if such a frame exists.
  /// </summary>
  /// <param name="path"> The path to backtrack from. </param>
  /// <returns> 
  ///  The next frame to investigate, which is represents the first unexplored 
  ///  child of the node closest to the top of the stack which has any unexplored 
  ///  children left. If no such a frame exists <c>null</c> is returned and the search 
  ///  should be stopped. 
  /// </returns>
  private static Frame Backtrack(Stack<Frame> path)
  {
    Frame nextFrame = null;
    do
    {
      path.Pop();
    }
    while (path.Count > 0 && !DescendToNextChild(path, out nextFrame));

    return nextFrame;
  }
}

这是一个很好的脑筋急转弯和欢迎分心。虽然我没有彻底测试它,但我运行了不同的场景:没有路径存在,路径存在,循环存在,它们都返回了有效的结果。

棘手的部分(概念上)是跟踪您当前正在降级的子路径。我将其存储在Frame.NextChild中。

更新:我重构了代码。主循环现在非常简单,两个主要概念(降序和回溯)现在很好地封装在不同的方法中。

答案 1 :(得分:1)

我会在你的Node课程

中添加一些内容
public class Node
{
    ......
    public Node PrevInPath{get;set;}
    public bool Visited {get;set;}
}

并且(我想你想找到一条从源到目的地的路径),我建议使用Queue来简单地找到它,另外你应该改进你的数据结构,目前你的数据结构很差,似乎你用函数式语言编写代码(不是c#):

private List<Node> FindWayFrom(
        Node srcNode,
        Node dstNode,
        Graph graph)
    {

       foreach(var node in graph)
         node.Visited = false;

    Queue<Node> Q = new Queue<Node>();
    srcNode.PrevInPath = null;
    srcNode.Visited = true;
    Q.Enqueue(srcNode);

    while(Q.Count()>0)
    {
       var currNode = Q.Dequeue();
       if (currNode == destNode)
         break;

       foreach(var node in currNode.Adjacent)
       {
           if (node.Visited == false)
           {
               node.Visited = true;
               node.PrevInPath = currNode;
           }
       }

    }

    if (destNode.Visited)
    {
       var path = List<Node>();
       var currNode = destNode;
       while (currNode != srcNode)
       {
           path.Add(currNode);
           currNode = currNode.PrevInPath;
       }
       return path.Reverse().ToList();
    }
    return null;
}

未经过测试的代码可能存在编译错误且效率不高,但只是可以修复,但是Idea正在使用队列,并标记被访问节点,也用于跟踪路径,您应该获得有关当前创建路径的一些信息,然后向后输出它。

答案 2 :(得分:0)

最常见的方法是将对象推送到堆栈,就像进行函数调用一样,并在迭代内对该堆栈进行操作

Way to go from recursion to iteration

答案 3 :(得分:0)

您需要使用堆栈而不是重新调用例程。

在整个例程周围放置一个while循环,检查本地堆栈是否有项目,如果它确实弹出了该项目。

您再次调用该方法的行会将这些详细信息推送到堆栈中。

在例程开始时按下传入的方法详细信息。