简体   繁体   English

递归函数的空间复杂度是否最小为O(N)?

[英]Do recursive functions have a minimum space complexity of O(N)?

I was thinking about recursive functions. 我在考虑递归函数。 Take a simple function, for example one to print a linked list recursively: 采用一个简单的函数,例如一个递归打印链表:

void print(list *list){
  if(list){
     cout << list->data
     print(list->next);
  }
}

At first this seems like a pretty innocuous function, but isn't it storing an address (labeled by the variable list) in every stack frame? 起初,这似乎是一个非常无害的功能,但它不是在每个堆栈帧中存储一个地址(由变量列表标记)? Assume there is no tail-call optimization. 假设没有尾调用优化。 We need space for N addresses, where N is the size of the list. 我们需要N个地址的空间,其中N是列表的大小。 The space needed grows linearly in proportion to the size of the list. 所需空间与列表大小成比例地线性增长。

I can't think of how you would implement a recursive function without having at least one local variable or argument stored on the stack. 我想不出如何在没有至少一个局部变量或参数存储在堆栈中的情况下实现递归函数。 So, it seems as though every recursive function is going to have at best linear space complexity. 因此,似乎每个递归函数都具有最佳线性空间复杂度。 If this is the case, then wouldn't it almost always be advisable to use iteration over recursion? 如果是这种情况,那么在递归上使用迭代几乎总是不可取的吗?

While all non-optimized function calls can be assumed to consume a stack frame, it's not always the case that a recursive algorithm that operates on N elements will require a stack O(N) in size. 虽然可以假设所有非优化函数调用都使用堆栈帧,但并非总是如此,对N个元素进行操作的递归算法将需要堆栈O(N)的大小。

A recursive tree-traversal algorithm uses O(lg N) stack frames, for example, as does a recursive QuickSort. 递归树遍历算法使用O(lg N)堆栈帧,例如,递归QuickSort。

You're right, the space complexity of the piece of code is linear in the size of the list, assuming no tail call optimization. 你是对的,假设没有尾调用优化,代码片段的空间复杂度在列表大小上是线性的。

In general, recursion will make things a little slower and more memory hungry, yes. 一般来说,递归会使事情变得更慢,更多的内存饥饿,是的。 But you can't always asymptotically improve on this by implementing it iteratively, since for non-tail recursive functions, you will need to manually maintain the stack anyway in an iterative implementation, so you will still use the same amount of memory. 但是你不能总是通过迭代实现它来渐进地改进它,因为对于非尾递归函数,你需要在迭代实现中手动维护堆栈,所以你仍然会使用相同数量的内存。

Think of a depth first traversal. 想一想深度优先遍历。 You will need to store each node on the stack, together with which child you need to visit next, so that after you return from visiting one of its children, you know which node to go to next. 您需要将每个节点存储在堆栈中,以及下一个需要访问的子节点,以便在您访问其中一个子节点后返回后,您将知道要转到下一个节点。 Recursion makes this very easy, since it abstracts all the ugly bookkeeping. 递归使这很容易,因为它抽象了所有丑陋的簿记。 An iterative implementation will not be asymptotically better, and I'd expect the practical differences to be very, very little as well. 迭代实现不会渐近更好,我希望实际差异也非常非常小。

A lot of times, recursion makes things easier without sacrificing anything. 很多时候,递归使得事情变得更容易,而不会牺牲任何东西。 In your case, there is no point for it - it's just a pedagogical example of recursion. 在你的情况下,它没有意义 - 它只是一个递归的教学例子。

You're correct. 你说的没错。 You don't even need variables, the return address itself already occupies space. 你甚至不需要变量,返回地址本身已占用空间。 There are methods to avoid deep nesting in recursion (tail recursion) and modern compilers do it automatically in many cases. 有一些方法可以避免递归中的深度嵌套(尾递归),现代编译器在许多情况下会自动执行。 But besides that, the iteration will be preferable over recursion from space complexity aspect. 但除此之外,迭代将优于空间复杂性方面的递归。

答案是否定的,例如(完整)二叉树的遍历具有O(log N)的空间复杂度,即树的深度。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM