如果我们考虑C / C ++中的递归函数,它们在任何方面都有用吗?它们究竟在哪里使用? 使用递归函数在内存方面有什么优势吗?
编辑:递归更好还是使用while循环?
答案 0 :(得分:10)
递归函数主要用于简化设计算法。例如,你需要递归遍历一个目录树 - 它的深度有限,所以你很可能永远不会面对太深的递归和随之而来的堆栈溢出,但是递归地编写树遍历会更容易,然后做同样的事情。以迭代的方式。
在大多数情况下,与迭代解决方案相比,递归函数不会节省内存。更糟糕的是,他们消耗相对稀疏的堆栈内存。
答案 1 :(得分:4)
答案 2 :(得分:4)
递归最终在递归性问题上具有优势。其他海报以其中一些命名。
将C的功能用于递归最终在内存管理方面具有优势。当您尝试避免递归时,大多数情况下使用自己的堆栈或其他动态数据类型来解决问题。这涉及C / C ++中的动态内存管理。动态内存管理成本高昂且错误!
你无法击败筹码
另一方面,当你只使用堆栈并使用局部变量的递归时 - 内存管理很简单,堆栈大部分时间都比你自己做的所有内存管理更节省时间或者使用普通的C / C ++内存管理。原因是系统堆栈是一种简单方便的数据结构,开销很低,并且使用针对此工作优化的特殊处理器操作来实现。相信我,你无法战胜,因为编译器,操作系统和处理器已经针对堆栈操作进行了几十年的优化!
PS:堆栈也不会碎片化,就像堆内存容易。这样,也可以通过使用堆栈/递归来节省内存。
答案 3 :(得分:3)
使用和不使用递归实现 QuickSort ,然后您可以自己判断它是否有用。
答案 4 :(得分:2)
我经常发现递归算法更容易理解,因为它们涉及较少的可变状态。考虑用于确定两个数的最大公约数的算法。
unsigned greatest_common_divisor_iter(unsigned x, unsigned y)
{
while (y != 0)
{
unsigned temp = x % y;
x = y;
y = temp;
}
return x;
}
unsigned greatest_common_divisor(unsigned x, unsigned y)
{
return y == 0 ? x : greatest_common_divisor(y, x % y);
}
根据我的口味,迭代版本中的重命名太多了。在递归版本中,所有内容都是不可变的,因此如果您愿意,甚至可以创建x和y const
。
答案 5 :(得分:1)
使用递归时,您可以将数据存储在堆栈上(实际上,在当前实例上方的所有函数的调用上下文中),如果您尝试执行此操作,则可以使用动态分配将其存储在堆中与while
循环相同的事情。
想想大多数分而治之的算法,每次调用都要做两件事(也就是说,其中一个调用不是尾递归的)。
关于Tom的有趣评论/子问题,递归函数的这种优势在C中可能特别明显,其中动态内存的管理是如此基本。但这并不能使它对C非常具体。
答案 6 :(得分:1)
Dynamic programming是递归至关重要的关键领域,尽管它超越了这一点(记住子答案可以带来显着的性能提升)。算法是通常使用递归的地方,而不是典型的日常编码。它更像是计算机 - 科学概念,而不是编程概念。
答案 7 :(得分:1)
值得一提的是,在大多数函数式语言(例如Scheme)中,您可以利用尾部调用优化,因此您可以使用递归函数而不会增加堆栈中的内存量。
基本上,复杂的递归尾调用可以在Scheme中完美运行,而在C / C ++中,相同的会产生堆栈溢出。
答案 8 :(得分:0)
我看到使用递归有两个原因:
小心处理递归,因为总是存在无限递归的危险。
答案 9 :(得分:0)
递归函数可以更轻松地编写具有recurrence relation。
的解决方案例如,阶乘函数具有递归关系:
factorial(0) = 1
factorial(n) = n * factorial(n-1)
下面我使用递归和循环实现了阶乘。
上面定义的递归版本和递归关系看起来很相似 因此更容易阅读。
递归:
double factorial ( int n )
{
return ( n ? n * factorial ( n-1 ) : 1 );
}
循环:
double factorial ( int n )
{
double result = 1;
while ( n > 1 )
{
result = result * n;
n--;
}
return result;
}
还有一件事:
factorial的递归版本包括对自身的尾调用,并且可以进行尾调用优化。这使得递归阶乘的空间复杂度降低到迭代阶乘的空间复杂度。