-
09-06-2019 - |
题
有效的打击,如果我们使用一个循环,而不是递归或反之亦然算法,其中既可以服务于同一目的?例如:如果检查了给予串是一个回文.我已经看到许多程序员使用递归作为一种手段来显示,当一个简单的迭代算法可以适合该法案。并编译器发挥至关重要的作用,在决定使用什么样的?
其他提示
循环可能实现的性能增加你的节目。递归可以实现的性能增加你的程序员。选择哪个更重要的是在你的情况!
比较递归的迭代是比较喜欢菲利普螺丝刀给一个平头螺丝刀。大部分你 可能 删除任菲利普斯的头螺钉有一个平头,但它只会更加容易,如果您使用的螺丝起子设计的螺丝对吗?
一些算法只是借给自己递归的方式,因为他们的目的是(斐波那契数列,穿越树似的结构,等等)。递归使得算法更为简洁的和更易于理解的(因此可共享和可重复使用).
此外,某些递归算法的使用"懒惰的评价",这使得他们更有效地比他们的迭代的兄弟。这意味着他们只能做昂贵的计算,当时他们正需要,而不是每次循环的运行。
这应该足以让你开始。我会挖掘的一些文章和实例。
链接,1: Haskel vs PHP(递归vs迭代)
这里是一个例子,其中程序员不得不处理一个大数据集使用PHP.他表示多么容易就已经处理在Haskel使用递归,但由于PHP没有简单的方法来完成相同的方法,他被迫使用的次迭代要得到的结果。
http://blog.webspecies.co.uk/2011-05-31/lazy-evaluation-with-php.html
链路2: 掌握递归
最递归的坏名声来自高的费用和低效率,在必要的语言。这篇文章的作者谈到如何优化递归算法,以使其更快和更有效率。他还去了如何将传统的循环转递归功能和使用的尾端递归。他的闭幕词真总结了我的一些要点,我认为:
"递归的编程让程序的更好的方法组织 代码的方式,既可维护和在逻辑上保持一致。"
链接3: 是递归永远快于循环?(答复)
这里是一个链接到一个答案为一个计算器问题,这是你的相似。提交人指出,大量的基准相关联的用递归访问或循环是 非常 语言,特定的。必要的语言通常更快的采用一个循环和慢用递归,反之亦然对于功能性的语言。我想要点采取从这个链接的是,它是非常难以回答这个问题的一种语文不可知论者/盲情况的意义。
递归是更昂贵的存储器,作为每个递归的呼吁,一般都需要有记忆的地址被推到叠,以便以后的程序可能返回这一点。
仍然,也有许多情况下,在递归的是更多的自然和可读于循环-像工作时与树木。在这些情况下,我建议坚持递归。
通常,人们期待的表现的刑罚躺在其他方向。Recursive calls可以导致建设的额外堆帧;在处罚这种变化。此外,在一些语言,如Python(更正确地说,在一些实施中的某些语言...),可以运行成堆的限制而不是容易的任务你可能指定递归,如发现最大价值在一棵树的数据结构。在这些情况下,你真的想要坚持循环。
写好递归功能可以降低性能的惩罚,假设你有一个编译器,优化尾递归,等等。(还的双重检查,以确保功能真的是尾递它的那些东西,许多人犯错误。)
除了"边缘"的情况(高性能计算,非常大的递归的深度,等等), 这是最好采用的方法,最清楚地表达自己的意图,是精心设计的,并可维护。优化后,才确定的需要。
递归是更好的比迭代的问题,可以分解成 多, ,较小的碎片。
例如,要使一种递归的排列算法,你打破fib(n)成fib(n-1)和fib(n-2)和计算两个部分。迭代只有让你重复一个单一的功能。
然而,斐波纳契数实际上是一个破碎的例子,我认为迭代实际上是更加有效。注意到fib(n)=fib(n-1)+fib(n-2)和fib(n-1)=fib(n-2)+fib(n-3).fib(n-1)获取计算两次!
一个更好的实例是递归算法的一棵树。问题的分析父节点可以分解成 多 较小的问题的分析每一个儿童节点。不同的是斐波那契例,较小的问题都是彼此独立的。
所以是的-递归是更好的比迭代的问题可以分为多个较小、独立的、类似的问题。
你的表现恶化时,使用递归因为调的方法,在任何语言暗示了很多的准备:叫码的员额返回的地址,调用参数,其他一些背景信息,例如处理器寄存器可能被保存的地方,并在返回时被叫方法的职位一个返回的价值,这是再检索到通过呼叫者和任何背景信息以前保存将得到恢复。性能差异之间的一种迭代和递归的办法在于时间的这些操作。
从执行的角度来看,你真的开始注意到的差异,当时需要处理调用的上下文中是相当于所需的时间对你的方法来执行。如果你递归的方法需要更长的时间来执行那叫境管理的一部分,转递归的方式作为代码是一般更易读和易于理解的,你不会注意到的性能损失。否则迭代的效率的原因。
我相信尾递归java目前不是优化。细节是撒全 此 讨论LtU和相关链接。它的 可 是的一个特征,在即将到来的第7版,但显然它提出了某些困难时结合堆的检验,因为某些框架将会丢失。堆的检查已用来实现他们的精细的安全模型,因为Java2.
在许多情况下,它提供了一个更优雅的解决方案的迭代方法,常见的例子是穿越二树,使它不一定是更加难以维持。在一般情况下,迭代的版本通常更快一点(和优化期间可能取代一个递归的版本),但递归的版本更易于理解和执行正确的。
递归是非常有用的是,一些情况。例如,考虑码找到的因子
int factorial ( int input )
{
int x, fact = 1;
for ( x = input; x > 1; x--)
fact *= x;
return fact;
}
现在考虑通过利用递归功能
int factorial ( int input )
{
if (input == 0)
{
return 1;
}
return input * factorial(input - 1);
}
通过观察这两个,我们可以看到,递归是很容易理解。但是,如果它不是小心使用它可以更容易出错。假设如果我们错过 if (input == 0)
, 然后该守则将是执行一些时间和结束与通常一堆溢出。
在许多情况下递归速度更快,因为缓存,从而提高性能。例如,这里是一个迭代的版本合并排序使用传统的合并程序。它将运行速度慢于递归的执行情况,因为缓存改进表演。
迭代执行情况
public static void sort(Comparable[] a)
{
int N = a.length;
aux = new Comparable[N];
for (int sz = 1; sz < N; sz = sz+sz)
for (int lo = 0; lo < N-sz; lo += sz+sz)
merge(a, lo, lo+sz-1, Math.min(lo+sz+sz-1, N-1));
}
递归的执行情况
private static void sort(Comparable[] a, Comparable[] aux, int lo, int hi)
{
if (hi <= lo) return;
int mid = lo + (hi - lo) / 2;
sort(a, aux, lo, mid);
sort(a, aux, mid+1, hi);
merge(a, aux, lo, mid, hi);
}
PS-这是什么告诉教授凯文*韦恩(普林斯顿大学)的课程上的算法提出的在课程.
采用递归,你正在承担成本的一个函数调与每个"迭代",而有一个循环,唯一的事情,你通常支付是一种递增/减量。因此,如果该循环的代码不是要复杂得多的代码递归的解决方案、循环通常将优于递归。
递归和迭代取决于业务的逻辑,你希望实现,虽然在大多数情况下,它可以互换使用。大多数开发人员转递归的,因为它是更容易理解。
它取决于语言。在Java你应该使用循环。功能性的语言优化递归。
如果你刚刚重复过一个名单,然后确定,迭代的距离。
几个其他的答案已经提到(深度第一次)树遍历。这真的是这样的一个很好的例子,因为这是一个很常见的事情要做一个非常共同的数据结构。递归是非常直观对这个问题。
检查"发现"的方法:http://penguin.ewu.edu/cscd300/Topic/BSTintro/index.html
我想在(非尾)递归会有一个业绩打分配一个新的堆等等,每一次的功能是被称为(依赖于语言的课程).
它取决于"递归深度"。它取决于多功能呼叫开销将会影响总的执行时间。
例如,计算典型的因子在递归的方式是非常由于效率低下:-风险的数据的满溢 -风险的堆满溢 -功能呼叫开销的占用80%的执行时间
虽然发展中一个最小值-最大值算法位置分析在游戏中的棋,将分析后续N动可以在递归在"分析的深度"(为我做的^_^)
递归?我从哪里开始,维基会告诉你"这是该进程重复的项目中有自相似的方式"
背在一天当我在做C、C++递归上帝发送,这样的东西"尾递归".你还会找到许多排序的算法使用递归。快速排序,例如: http://alienryderflex.com/quicksort/
递归是像任何其他算法用于一个特定的问题。也许你可能不会找到一个使用直接或者通常但不会有问题你会很高兴。
C++如果递归功能是一个模板,随后编译器有更多的机会,以优化它,因为所有类型扣减和功能的实例将发生在汇编的时间。现代的编译器,也可以联的功能,如果可能的。因此,如果一个使用最优化的标志喜欢 -O3
或 -O2
在 g++
, 然后递归可以有机会以更快的速度比迭代。在迭代码时,编译器获取的机会较少,以优化它,因为它已经在或多或少的最佳状态(如果写的不够好).
在我的情况下,我是在努力执行矩阵幂通过平方使用犰狳矩阵的对象,在这两种递归和迭代办法。算法可以在这里找到... https://en.wikipedia.org/wiki/Exponentiation_by_squaring.我的功能的模板和我已经计算出了 1,000,000
12x12
矩阵提出的来电 10
.我得到了以下结果:
iterative + optimisation flag -O3 -> 2.79.. sec
recursive + optimisation flag -O3 -> 1.32.. sec
iterative + No-optimisation flag -> 2.83.. sec
recursive + No-optimisation flag -> 4.15.. sec
这些结果已经得到使用的海湾合作委员会-4.8用c++11标志(-std=c++11
)和犰狳6.1与英特尔mkl.英特尔编译器也表示了类似的结果。
麦克是正确的。尾递归 不 优化了通过Java编译器或JVM。你总是会得到一堆的溢出这样的事情:
int count(int i) {
return i >= 100000000 ? i : count(i+1);
}
你必须记住,利用太深递归你会遇到一堆溢出,根据允许堆大小。防止此一定要提供一些基本情况下结束你递归。
递归有一个缺点,算法的编写使用递归已O(n)空间复杂性。同时迭代方法有一个空间的复杂程度O(1)。这是advantange使用的迭代过递归。那么为什么我们使用递归?
见下文。
有时更易于编写一个算法的使用递归,同时它稍微更严厉的编写同样的算法采用的迭代。在这种情况下,如果你选择遵循的迭代方法,你会必须处理的堆自己。
据我所知,Perl不优化尾递归的话,但是可以假。
sub f{
my($l,$r) = @_;
if( $l >= $r ){
return $l;
} else {
# return f( $l+1, $r );
@_ = ( $l+1, $r );
goto &f;
}
}
当第一被称为它将分配上的空间堆。然后它会改变其论点,并重新启动中子程序,而不加任何东西更多。因此,它将假装那从来没有所谓的其自主,改变成一个迭代的过程。
注意到没有"my @_;
"或者"local @_;
"如果你做了,它将不再起作用。
只用铬45.0.2454.85m,递归似乎是一个很好的量更快。
这里是代码:
(function recursionVsForLoop(global) {
"use strict";
// Perf test
function perfTest() {}
perfTest.prototype.do = function(ns, fn) {
console.time(ns);
fn();
console.timeEnd(ns);
};
// Recursion method
(function recur() {
var count = 0;
global.recurFn = function recurFn(fn, cycles) {
fn();
count = count + 1;
if (count !== cycles) recurFn(fn, cycles);
};
})();
// Looped method
function loopFn(fn, cycles) {
for (var i = 0; i < cycles; i++) {
fn();
}
}
// Tests
var curTest = new perfTest(),
testsToRun = 100;
curTest.do('recursion', function() {
recurFn(function() {
console.log('a recur run.');
}, testsToRun);
});
curTest.do('loop', function() {
loopFn(function() {
console.log('a loop run.');
}, testsToRun);
});
})(window);
结果,
//100运行使用的标准回路
100循环的运行。时间完成: 7.683ms
//100运行,使用递归功能的办法w/尾递归
100递归运行。时间完成: 4.841ms
在下图,递归赢得再次通过一个更大的保证金时运行300次,每次测试
如果迭代原子弹和数量级更加昂贵于推动一个新的框架堆 和 创建一个新的螺纹 和 你有多个核心 和 你的运行环境可以使用所有的人,然后递归的做法可能会产生一个巨大的性能提高当结合多线程。如果平均数目的迭代,不可预测,那么它可能是一个好主意,使用一线的游泳池,这将控制线的分配和防止你进程创造太多线程和占用该系统。
例如,在一些语言,有recursive多线程的合并排序的方式实现的。
但是,多可以使用的循环,而不是递归,所以如何以及这种组合将取决于多个因素,其中包括操作系统和其螺纹分配机制。
我会回答你的问题通过设计一个Haskell的数据结构通过"诱导",这是一种"双重"到递归。然后我将展示如何这种双重性导致了美好的事情。
我们引进一种类型的简单树:
data Tree a = Branch (Tree a) (Tree a)
| Leaf a
deriving (Eq)
我们可以阅读这一定义的话说,"树是一个分支(其中包含两种树)或是一片叶子(其中包含有数据的价值)".因此叶是一种极少的情况。如果一棵树并不是一片叶子,那么它必须是一个化合物树含有两种树木。这是唯一的情况。
让我们树:
example :: Tree Int
example = Branch (Leaf 1)
(Branch (Leaf 2)
(Leaf 3))
现在,让我们假设,我们想要增加1中的每一个值树。我们可以做到这一呼吁:
addOne :: Tree Int -> Tree Int
addOne (Branch a b) = Branch (addOne a) (addOne b)
addOne (Leaf a) = Leaf (a + 1)
首先,请注意,这实际上是一种递归的定义。它需要的数据的构造枝和叶作为的情况下(自叶是最小的,这是唯一可能的情况下),我们相信,该功能将终止。
那会是什么需要写addOne在一个迭代的风格?什么将循环变成一个任意数量的分支机构看起来像什么?
此外,这种递归,往往可以考虑出,在条款"函子".我们可以使树木成函的限定:
instance Functor Tree where fmap f (Leaf a) = Leaf (f a)
fmap f (Branch a b) = Branch (fmap f a) (fmap f b)
和定义:
addOne' = fmap (+1)
我们可以因素除其他递归方案,例如catamorphism(或折叠)对于一个代数数据类型。使用catamorphism,我们可以这样写:
addOne'' = cata go where
go (Leaf a) = Leaf (a + 1)
go (Branch a b) = Branch a b
堆溢只会发生,如果你编程语言,并没有建立存管理。...否则,确保你有东西在你的功能(或功能的电话,STDLbs等)。没有递归它只会不可能拥有的东西喜欢...谷歌或SQL,或任何地方的一个必须有效地进行排序,通过大型的数据结构(班级)或数据库。
递归的路要走,如果你想要迭代通过的文件,这肯定是如何找到*|?查询*'的工作。有点双递归,特别是与管道(但不要做一堆系统调用像这么多样做,如果这是什么你要把那里为其他人使用)。
更高水平的语言,甚至铛/cpp可以实施相同的背景。