为什么我的递归Fibonacci实现与迭代实现相比如此之慢?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么我的递归Fibonacci实现与迭代实现相比如此之慢?相关的知识,希望对你有一定的参考价值。
我创建了以下简单的Fibonacci实现:
#![feature(test)]
extern crate test;
pub fn fibonacci_recursive(n: u32) -> u32 {
match n {
0 => 0,
1 => 1,
_ => fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2)
}
}
pub fn fibonacci_imperative(n: u32) -> u32 {
match n {
0 => 0,
1 => 1,
_ => {
let mut penultimate;
let mut last = 1;
let mut fib = 0;
for _ in 0..n {
penultimate = last;
last = fib;
fib = penultimate + last;
}
fib
}
}
}
我创建它们来试用cargo bench
,所以我写了以下基准:
#[cfg(test)]
mod tests {
use super::*;
use test::Bencher;
#[bench]
fn bench_fibonacci_recursive(b: &mut Bencher) {
b.iter(|| {
let n = test::black_box(20);
fibonacci_recursive(n)
});
}
#[bench]
fn bench_fibonacci_imperative(b: &mut Bencher) {
b.iter(|| {
let n = test::black_box(20);
fibonacci_imperative(n)
});
}
}
我知道递归实现通常比命令式实现慢,特别是因为Rust不支持尾递归优化(这种实现无论如何都不能使用)。但我没想到近2 000次以下差异:
running 2 tests
test tests::bench_fibonacci_imperative ... bench: 15 ns/iter (+/- 3)
test tests::bench_fibonacci_recursive ... bench: 28,435 ns/iter (+/- 1,114)
我在Windows和Ubuntu上使用最新的Rust nightly编译器(rustc 1.25.0-nightly
)运行它并获得了类似的结果。
这种速度差异是否正常?我写了一些“错误的”吗?或者我的基准有缺陷吗?
答案
正如Shepmaster所说,你应该使用累加器来保留先前计算的fib(n - 1)
和fib(n - 2)
,否则你会继续计算相同的值:
pub fn fibonacci_recursive(n: u32) -> u32 {
fn inner(n: u32, penultimate: u32, last: u32) -> u32 {
match n {
0 => penultimate,
1 => last,
_ => inner(n - 1, last, penultimate + last),
}
}
inner(n, 0, 1)
}
fn main() {
assert_eq!(fibonacci_recursive(0), 0);
assert_eq!(fibonacci_recursive(1), 1);
assert_eq!(fibonacci_recursive(2), 1);
assert_eq!(fibonacci_recursive(20), 6765);
}
last
相当于fib(n - 1)
。
penultimate
相当于fib(n - 2)
。
另一答案
两种实现之间的算法复杂性不同:
- 你的迭代实现使用累加器:O(N),
- 你的递归实现不是:O(1.6N)。
从20(N)<< 12089(1.6N)开始,有很大差异是很正常的。
请参阅this answer,了解天真实现案例中复杂性的精确计算。
注意:用于迭代情况的方法称为dynamic programming。
以上是关于为什么我的递归Fibonacci实现与迭代实现相比如此之慢?的主要内容,如果未能解决你的问题,请参考以下文章