我正在编写一个“简单”程序来确定Fibonacci序列中的第N个数字。例如:序列中的第7个数字是:13。我已经完成了程序的编写,它可以工作,但从第40个数字开始它开始延迟,并且需要更长,更长。我的节目必须进入系列中的第100个位置。
我如何解决这个问题,所以它不需要这么长时间?这是非常基本的程序,所以我不知道所有花哨的语法代码..我的公式是:
if n =1 || n = 0
return n;
else
return F(n-1) + F(n-2);
这很有效,直到它超过第40个学期。我还需要添加哪些其他语句才能更快地获得更高的数字?
答案 0 :(得分:10)
问题是因为你使用的是简单的递归,所以你多次重新评估F(n),所以你的执行时间是指数级的。
有两种简单的方法可以解决这个问题:
1)第一次评估F(n)时的缓存值。在评估F(n)之前先检查缓存,看看你是否已经为此计算了它。
2)使用迭代方法:计算F(1),F(2),F(3)等......直到达到所需的数字。
答案 1 :(得分:7)
问题在于你的算法虽然在数学上是纯粹的(并且很好)但并不是很好 对于它想要计算的每个数字,它必须计算两个较低的数字,而这又需要计算两个较低的数字等。您当前的算法的Big O notation复杂度约为O(1.6n),所以非常大数字(例如100)需要很长时间。
本书Structure and Interpretation of Computer programs has a nice diagram:显示使用算法生成fib 5
时会发生什么情况
(来源:mit.edu)
最简单的方法是存储F-1和F-2,这样您就不必每次都从头开始计算它们。换句话说,使用循环而不是使用递归。比意味着算法的复杂性从O(1.6 n )到O(n)。
答案 2 :(得分:6)
有许多解决方案。最直接的是使用memoization。还有Binet's formula,它会在恒定时间内给你第n个斐波纳契数。
对于记忆,您将F [a_i]的结果存储在某种地图或列表中。例如,在天真的递归中,你计算F [4]数十万次。通过在找到它们时存储所有这些结果,递归就像树一样停止,看起来就像直接的迭代解决方案。
如果这不是作业,请使用Binet的公式。这是最快的方法。
答案 3 :(得分:4)
试试这个例子,它在合理的时间范围内计算出百万分之斐波纳契数,而不会有任何精度损失。
import java.math.BigInteger;
/*
250000th fib # is: 36356117010939561826426 .... 10243516470957309231046875
Time to compute: 3.5 seconds.
1000000th fib # is: 1953282128707757731632 .... 93411568996526838242546875
Time to compute: 58.1 seconds.
*/
public class Fib {
public static void main(String... args) {
int place = args.length > 0 ? Integer.parseInt(args[0]) : 1000 * 1000;
long start = System.nanoTime();
BigInteger fibNumber = fib(place);
long time = System.nanoTime() - start;
System.out.println(place + "th fib # is: " + fibNumber);
System.out.printf("Time to compute: %5.1f seconds.%n", time / 1.0e9);
}
private static BigInteger fib(int place) {
BigInteger a = new BigInteger("0");
BigInteger b = new BigInteger("1");
while (place-- > 1) {
BigInteger t = b;
b = a.add(b);
a = t;
}
return b;
}
}
答案 4 :(得分:2)
创建一个包含100个值的数组,然后在计算Fib(n)的值时,将其存储在数组中并使用该数组获取Fib(n-1)和Fib(n-2)的值。
如果您在不存储任何先前计算的值的情况下调用Fib(100),则会使您的Java运行时爆炸。
伪代码:
array[0] = 0;
array[1] = 1;
for 2:100
array[n] = array[n-1] + array[n-2];
答案 5 :(得分:1)
问题不在于JAVA,而在于您实现Fibonacci算法的方式。 您多次计算相同的值,这会减慢您的程序。
尝试这样的事情:Fibonacci with memoization
答案 6 :(得分:1)
F(n)
/ \
F(n-1) F(n-2)
/ \ / \
F(n-2) F(n-3) F(n-3) F(n-4)
/ \
F(n-3) F(n-4)
请注意,许多计算都会重复! 需要注意的重要一点是该算法是指数的,因为它不存储先前计算的数字的结果。例如,F(n-3)被称为3次。
更好的解决方案是下面写的迭代代码
function fib2(n) {
if n = 0
return 0
create an array f[0.... n]
f[0] = 0, f[1] = 1
for i = 2...n:
f[i] = f[i - 1] + f[i - 2]
return f[n]
}
有关详细信息,请参阅dasgupta第0.2章的算法
答案 7 :(得分:1)
我使用Java 8 Stream的解决方案:
public class Main {
public static void main(String[] args) {
int n = 10;
Fibonacci fibonacci = new Fibonacci();
LongStream.generate(fibonacci::next)
.skip(n)
.findFirst()
.ifPresent(System.out::println);
}
}
public class Fibonacci {
private long next = 1;
private long current = 1;
public long next() {
long result = current;
long previous = current;
current = next;
next = current + previous;
return result;
}
}
答案 8 :(得分:0)
如果你使用天真的方法,你最终会得到爆炸数量相同的计算,即钙质(n)必须钙化(n-1)和fib(n-2)。然后到calc fib(n-1)你必须钙化(n-2)和fib(n-3)等。更好的方法是反向。您以fib(0),fib(1),fib(2)开始计算并将值存储在表中。然后计算后续值,使用存储在表(数组)中的值。这也是cao memoization。试试这个,你应该能够计算大量的纤维数。
答案 9 :(得分:0)
这是Python中的代码,可以很容易地转换为C / Java。第一个是递归的,第二个是迭代解决方案。
def fibo(n, i=1, s=1, s_1=0):
if n <= i: return s
else: return fibo(n, i+1, s+s_1, s)
def fibo_iter_code(n):
s, s_1 = 1, 0
for i in range(n-1):
temp = s
s, s_1 = s+s_1, temp
print(s)
答案 10 :(得分:0)
太慢......
更好: (JavaScript示例)
function fibonacci(n) {
var a = 0, b = 1;
for (var i = 0; i < n; i++) {
a += b;
b = a - b;
}
return a;
}
答案 11 :(得分:0)
import java.util.*;
public class FibonacciNumber
{
public static void main(String[] args)
{
int high = 1, low = 1;
int num;
Scanner in = new Scanner(System.in);
try
{
System.out.print("Enter Number : " );
num = in.nextInt();
System.out.println( low);
while(high < num && num < 2000000000)
{
System.out.println(high);
high = low + high;
low = high - low;
}
} catch (InputMismatchException e) {
System.out.print("Limit Exceeded");
}
}
}
/* Ouput :
Enter Number : 1999999999
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
10946
17711
28657
46368
75025
121393
196418
317811
514229
832040
1346269
2178309
3524578
5702887
9227465
14930352
24157817
39088169
63245986
102334155
165580141
267914296
433494437
701408733
1134903170
1836311903
-1323752223
512559680
-811192543
-298632863
-1109825406
-1408458269
1776683621
368225352 */
答案 12 :(得分:0)
朴素的实现是自然而优雅的,但在执行过程中,递归调用正在创建二叉树。除了已经提到的memoization,兑现以前的F(n)结果并避免不必要的树遍历,你可以进行尾调用优化,已经提到了迭代或矩阵乘法。例如,Java 8 memoization:
private static final Map<Long, Long> memo = new HashMap<>();
static {
memo.put(0L, 0L);
memo.put(1L, 1L);
}
public static void main(String[] args) {
System.out.println(fibonacci(0));
System.out.println(fibonacci(43));
System.out.println(fibonacci(92));
}
public static long fibonacci(long n) {
return memo.computeIfAbsent(n, m -> fibonacci(m - 1) + fibonacci(m - 2));
}
或者尾部调用优化版本:
interface FewArgs<T, U, V, R> {
public R apply(T t, U u, V v);
}
static FewArgs<Long, Long, Long, Long> tailRecursive;
static {
tailRecursive = (a, b, n) -> {
if (n > 0)
return tailRecursive.apply(b, a + b, n - 1);
return a;
};
}
你用a = 0,b = 1来调用它,n是第n个Fibonacci数,但必须小于93。 更有效的方法来计算斐波纳契数是矩阵平方,你会在我的博客上找到例子,比特公式
答案 13 :(得分:0)
您可以使用缓存技术。由于f(n)= f(n-1)+ f(n-2),当计算f(n-1)时,你将再次计算f(n-2)。所以简单地将它们视为两个增量数字,如下所示:
public int fib(int ithNumber) {
int prev = 0;
int current = 1;
int newValue;
for (int i=1; i<ithNumber; i++) {
newValue = current + prev;
prev = current;
current = newValue;
}
return current;
}
答案 14 :(得分:0)
使用三元运算符的多个语句看起来更好。
static int fib(int n) {
return n > 5 ? fib(n-2) + fib(n-1)
: n < 2 || n == 5 ? n
: n - 1;
}