树的递归和非递归过程

时间:2010-01-25 12:16:12

标签: c++ data-structures oop


因为我们知道树是递归数据结构,我们使用recurrsion来编写树的过程,如BST的删除方法等。
复发的优点是,我们的程序变得非常小(例如,顺序遍历的代码只有4或5行)而不是非复原程序,这种程序虽然冗长但不像理解透视中的递归程序那么复杂。这就是为什么我讨厌复发,我更喜欢写非复原程序,我在二元搜索树和avl树中做到了这一点。
现在请详细说明,更喜欢非递归程序而不是反复程序是不好或好事。“

4 个答案:

答案 0 :(得分:6)

递归是一种与其他工具一样的工具。你没有拥有来使用所有可用的工具,但你至少应该理解它。

递归使得某类问题非常容易和优雅地解决,你对它的“仇恨”充其量也是非理性的。这只是一种不同的做事方式。

下面以递归和迭代的形式显示“规范”递归函数(阶乘),在我看来,递归形式更清楚地反映了f(1) = 1, f(n) = n*f(n-1) for n>1的数学定义。

Iterative:                    Recursive:
def fact(n):                  def fact(n):
    r = n                         if n == 1:
    while n > 1:                      return 1
        r = r * n                 return n * fact(n-1)
        n = n - 1
    return r

几乎只有 的地方我更喜欢递归的解决方案(对于非常适合递归的解决方案)是当堆栈大小的增长可能导致问题时(上面)阶乘函数可能是其中之一,因为堆栈增长依赖于n,但它也可能被编译器优化为迭代解决方案)。但是这个堆栈溢出很少发生,因为:

  1. 可以在必要时配置大多数堆栈。
  2. 递归(尤其是递归调用是函数中发生的最后事情的尾端递归)通常可以通过智能编译器优化为迭代解决方案。
  3. 我在递归情况下使用的大多数算法(例如平衡树等,如你所提到的)往往是O(logN),并且堆栈使用不会随着数据增加而快速增长。例如,您可以处理一个存储20亿个条目的16路树,只有7个堆栈级别(16 7 = ~26亿)。

答案 1 :(得分:2)

您应该阅读Tail Recursion。通常,如果编译器设法将尾递归应用于过程,那么它非常有效,如果不是,则不是这样。

同样重要的问题是编译器的最大重复深度 - 通常它受到堆栈大小的限制。这里的缺点是没有优雅的方法来处理堆栈溢出。

答案 2 :(得分:1)

递归很优雅,但容易出现堆栈溢出。尽可能使用尾端递归,使编译器有机会将其转换为迭代解决方案。

您肯定决定使用哪种工具 - 但请记住,大多数处理树状数据结构的算法通常都是递归实现的。通常的做法是,您的代码更容易阅读,而其他代码则不那么令人惊讶。

答案 3 :(得分:0)

递归是一种工具。有时使用“递归工具”使代码更容易阅读,但不一定更容易理解。

一般来说,递归解决方案往往是一个很好的候选者,其中解决特定问题的“分而治之”方法是自然的。

通常情况下,递归是一个很好的选择,你可以看一个问题并说“啊哈,如果我知道这个问题的简单变体的答案,我可以使用该解决方案来生成我想要的答案”和“最简单的问题是P,其解决方案是S“。然后,整体解决问题的代码归结为查看数据,简化数据,递归生成一个(更简单的)答案,然后从简单的答案转到整个答案。

如果我们考虑计算树的水平的问题,答案是树的高度比孩子的“最高/最深”的高度高1倍,并且叶子的高度是1。类似下面的代码。这个问题可以迭代解决,但实际上,你可以在自己的数据结构中重新实现调用堆栈。

def tree_height (tree):
  if tree.is_leaf():
    return 1

  childmax = 0;
  for child in tree.children():
    childmax=max(childmax, tree_height(child))

  return childmax+1

值得考虑的是,Tail Call Optimization可以使一些递归函数在不断的堆栈空间中运行。