我已经阅读了很多关于为什么更喜欢递归而不是迭代,但我还没有看到任何解释何时更喜欢迭代而不是递归的问题。
当然,我相信没有最好的工具可以解决所有问题。
例如,勺子和叉子都是吃工具,但勺子更适合吃米饭,而叉子更适合吃面条。
显然,如果我说叉子(或勺子)是最好吃的工具,我会变得狂热。
因此,它与递归或迭代相同,它们都是工具,它们各有各的利弊。
然而,在搜索了Googles和StackOverflow之后,虽然我在为什么递归比迭代更具可读性上找到了很多例子,但是当迭代时我仍然没有在上找到任何示例比递归更容易阅读。
因此,我试图找出迭代将更加可读的情况。就像古代人类认识叉子在处理面条时比勺子更好。
因此,我希望得到满足以下要求的答案:
答案 0 :(得分:2)
为避免基于意见的回答,我想扩展递归的含义:
在谈论递归时,区分递归定义的函数和生成递归过程的函数非常重要。
在这两个方面中,后者更为重要(假设您正在编写一个程序来解决实际问题)。鉴于迭代过程和递归过程之间的选择,前者是更好的,无论可读性或优雅,因为效率的差异是巨大的。
作为一个例子,考虑计算斐波纳契数,让我们在Haskell中做,以表明这不是关于循环与函数调用:
fib :: Int -> Int
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
这会生成一个递归过程,例如fib(4)
fib(4)
|
----------+----------
fib(3) fib(2)
| |
-----+---- -----+----
fib(2) fib(1) fib(1) fib(0)
| | | |
| 1 1 0
-----+----
fib(1) fib(0)
| |
1 0
将此与迭代过程(由递归定义的函数生成)进行比较:
fibIter :: Int -> Int
fibIter n = iter 1 0 n
where
iter :: Int -> Int -> Int -> Int
iter _ b 0 = b
iter a b m = iter (a+b) a (m-1)
fib(4)生成的过程是
fibIter 4
iter 1 0 4
iter 1 1 3
iter 2 1 2
iter 3 2 1
iter 5 3 0
3
请注意,递归过程会生成指数个函数调用,其中迭代过程执行线性数量的函数调用(如果消除尾调用,则只需要常量空间)。
在其他情况下,并且在使用尾部调用消除并具有循环结构的语言中,我说在编写循环或使用递归之间的选择是品味或编程约定的问题(基于此观点)
更新关于bubblesort实现的可读性:
可以说,递归公式更清晰 直接将算法表示为
- 再次,可以说 - 与循环和局部变量的迭代公式相比,需要更少的精神管理。
为清楚起见,我可能会写一些类似
的递归bubblesortbubbleSort :: Ord a => [a] -> [a]
bubbleSort [] = []
bubbleSort l = bubbleSort rest ++ [largest]
where tmp = bubbleUpLargest l
largest = last tmp
rest = init tmp
bubbleUpLargest (x:y:xs)
| x > y = y:bubbleUpLargest (x:xs)
| otherwise = x:bubbleUpLargest (y:xs)
bubbleUpLargest x = x
答案 1 :(得分:0)
基于迭代的代码 - 在大多数情况下 - 内存密集程度较低。也许这个问题的最佳答案在于 递归是如何工作的。
当程序使用递归重定向到函数的“start”时,C ++语言不会重用该函数的现有实例。而是创建函数的 new 实例,并将程序计数器移动到新实例的开头。旧实例的地址放在堆栈缓冲区上,等待新实例的返回。
但是,使用迭代时,程序计数器将移动到现有代码实例的开头。 无需筹码。
这就是在许多使用C / C ++的低级编程应用程序中强烈支持迭代的原因。编程微控制器时,您几乎不会想要使用递归,因为堆栈缓冲区只能容纳非常少量的函数地址。
然而,在某些情况下,递归才有意义。在信号处理领域,数学通常分解为需要递归的公式,并且您不能总是找到方程的封闭形式版本。在这种情况下,当在具有足够内存的计算机上运行时,递归可能是最佳选择。
此外,递归在导航文件树时非常有用。请考虑以下代码Code Example。特别注意他的void DFS(struct node *head)
函数的简单性。
事实是,有了足够的内存,递归可能非常有用。所以它并不是递归和迭代之间的竞争,而是理解每种方法的局限性。