简单的尾递归函数和循环一样有效吗?

时间:2012-12-19 11:24:51

标签: c# clr

有时我会发现自己编写尾递归函数。我一直在寻找高低,我发现在.NET框架中有尾递归,但我不确定在什么情况下我可以,在什么情况下我不能有效地使用尾递归。例如,我有一个简单的树,让我们称之为

public class Tree
{
  public Tree parent = null;

  public Tree(Tree parent)
  {
    this.parent = parent;
    this.children = new List<Tree>();
  }

  public List<Tree> children {get; private set;}

  public Tree root
  {
    get
    {
      return this.parent == null ? this : parent.root;
    }
  }
}

对于root属性,编译器会发出一个循环吗?会发出.tail吗?抖动是否会尊重.tail?什么都不会发生,算法会递归运行吗?最重要的是,我应该将其重写为迭代吗?

2 个答案:

答案 0 :(得分:9)

C#编译器永远不会发出tail前缀。

如果呼叫处于尾部位置,则F#将执行此操作。它取决于遍历树的顺序,尾递归是否适用。

在你的代码中,尾部没有任何东西。原因是使用三元运算符。如果代码被重写为使用if语句并返回每个分支,那么对parent.root的调用将处于尾部位置。

在优化方面,编译器(F#或IronScheme)通常会将尾递归调用转换为while (true) { ... }循环。这样做是因为它删除了尾调用和再次调用该方法的需要。

因此,如果允许C#发出尾调用(它不是),它可能会从以下变换:

public Tree root
{
  get
  {
    if (parent == null) return this;
    else return parent.root; // now in tail position
  }
}

到(只是一个猜测)

public Tree root
{
  get
  {
    Tree temp = this;
    while (true)
    {
      if (temp.parent == null)
      {
         return temp;
      }
      temp = temp.parent;
    }
  }
}

F#和IronScheme都进行了相同的转换。这称为tail call elimination(TCE)。是的,它会比C#版本快一点。 (我在C#,F#和IronScheme上通过微基准测试fib对此进行了测试

答案 1 :(得分:1)

答案类似other answer

关于速度。尾递归优化与小函数的循环没有什么不同。当尾调用优化触发它时,只需用“jmp”替换“call”指令(在x86上)。当进行相同的循环时,您将获得进入下一循环的相同“jmp”指令。你应该记住的一点是,整个函数体将是循环体,因此你应该尽量减小递归函数的大小。