Fibonacci Sequence in Java taking too long?

前端 未结 6 901
余生分开走
余生分开走 2021-01-13 18:44

I\'m trying to find the sum of the Fibonacci sequence in Java, but the run time is taking way too long (or is it suppose to?). This slows down anytime I use an integer past

相关标签:
6条回答
  • 2021-01-13 18:46

    Recursive solutions don't necessarily have to be slow. If you were to use this tail-recursive solution, you'd save up a lot of memory and still achieve great speed (e.g. Fib(10000) runs in 1.1s on my machine).

    Here n is the sequence number for which you're calculating Fibonacci number, while f0 and f1 are two accumulators, for previous and current Fibonacci numbers respectively.

    public class FibonacciRec {
        public static int fib(int n, int f0, int f1) {
            if (n == 0) {
                return f0;
            } else if (n == 1){
                return f1;
            } else {
                return fib(n-1, f1, f0+f1);
            }
        }
    
        public static void main(String[] args) {
            System.out.println(fib(10, 0, 1));
        }
    }
    
    0 讨论(0)
  • 2021-01-13 18:54

    You need to use long instead of int if you want to calculate the 50th Fibonacci number. The 50th Fibonacci number is 12586269025 and exceeds the maximum value of int (see http://www.maths.surrey.ac.uk/hosted-sites/R.Knott/Fibonacci/fibtable.html).

    A non-recursive algorithm is likely going to be faster, see http://planet.jboss.org/post/fibonacci_sequence_with_and_without_recursion for the different implementations.

    0 讨论(0)
  • 2021-01-13 19:05

    For n > 2, an invocation of your getSum(n) recursively invokes itself twice. Each of those invocations may recurse further. The total number of method invocations scales as 2^n, and 2^50 is a very large number. This poor scaling reflects the fact that the simple-minded recursive approach ends up needlessly recomputing the same results (e.g. fib(4)) a great many times, and it is why your program slows down so rapidly as you increase n.

    The negative return value you get after a certain point arises from exceeding the limits of data type int. You could get a larger limit with a wider data type, presumably long. If that's not enough then you would need to go to something like BigInteger, at a substantial performance penalty.

    0 讨论(0)
  • 2021-01-13 19:08

    first, use a long instead of an int, to avoid overflow.

    Secondly, use a non-recursive algorithm, as a recursive one exists in exponential time I think. A well designed non-recursive one will solve in linear time (I think).

    Example non-recursive

    static long getSum(int n){
    
        long[] fibonacci = new long[n];
        fibonacci[0] = 1;
        fibonacci[1] = 1;
    
        if (n==0) return 0;
        if (n==1 || n==2) return 1;
    
        for(int i = 2; i < n;i++){
            fibonacci[i] = fibonacci[i-1]+ finonacci[i-2];
        }
        return fibonacci[n-1];
    }
    

    I haven't tested this, but it should work.

    If you plan to call this method frequently, it might be prudent to store the array outside of the method, so that it is a simple lookup when doing this. This would provide a constant time solution for numbers that have already been calculated at least once. an example of that is below.

    static long[] fibonacci= {1,1};
    static long getSum(int n){
    
        if (n==0) return 0;
        if (n==1 || n==2) return 1;
    
        int old_length = fibonacci.length;
    
        if(fibonacci.length < (n-1)){
            fibonacci = Arrays.copyOf(fibonacci,n);
        }else{
            return fibonacci[n-1];
        }
    
    
    
        for(int i = old_length; i < n;i++){
            fibonacci[i] = fibonacci[i-1]+ finonacci[i-2];
        }
        return fibonacci[n-1];
    }
    

    Again, the example is untested, so a bit of debugging might be required.

    Here is a linear time implementation of the algorithm that uses a constant overhead, instead of linear overhead.

    static long getSum(int n){
    
        long currentNum = 0;
        long previousNum = 1;
        long previousNum2 = 1;
    
        if (n==0) return 0;
        if (n==1 || n==2) return 1;
    
        for(int i = 2; i < n;i++){
            currentNum = previousNum+ previousNum2;
            previousNum2 = previousNum;
            previousNum = currentNum;
        }
        return currentNum;
    }
    
    0 讨论(0)
  • 2021-01-13 19:10

    As the others already stated you should use long for the calculated fibonacci value, as the number will get very long very fast.

    If your formost priority is performance you could use the following formula:


    with

    (Idea taken from Linear Algebra lecture, actual formula taken from Wikipedia.)
    That way you will get the n-th fibonacci number in constant time (depending on the calculation of the n-th powers in the formula).


    The following code calculates the fibonacci sequenc of the first 93 numbers with no waiting time (on my machine):

    private static final double SQRT_FIVE = Math.sqrt(5);
    private static final double GOLDEN_RATIO = (1 + SQRT_FIVE) / 2;
    
    public static void main(String[] args) {
        for(int i = 0; i <= 92; i++) {
            System.out.println("fib(" + i + ") = " + calculateFibonacci(i));
        }
    }
    
    public static long calculateFibonacci(int n) {
        double numerator = Math.pow(GOLDEN_RATIO, n) - Math.pow(1-GOLDEN_RATIO, n);
        double denominator = SQRT_FIVE;
    
        // This cast should in general work, as the result is always an integer. 
        // Floating point errors may occur!
        return (long)(numerator/denominator); 
    }
    

    From the 94-th number on the long is no longer sufficent and you need to use BigInteger and fitting math operations, as the double calculations may produce calculation errors with such big numbers.

    0 讨论(0)
  • 2021-01-13 19:13

    If you want to keep the recursive approach as is, cache results of calculation in an array or map. When you have calculated one Fibonacci for n, save that result. Then, in your method first see if you have the result and return that if you do. Otherwise, make the recursive call(s). Here's an example: recursion is still used and it is quite fast:

    public static Map<Long,Long> cache = null;
    
    public static void main(String[] args) {
        cache = new HashMap<Long,Long>();
        cache.put(0L,0L);
        cache.put(1L,1L);
        cache.put(2L,1L);
        Long sum=getSum(50L);
        System.out.println("Sum of Fibonacci Numbers is " + sum); 
    
    }
    
    static Long getSum(Long n){
        if (cache.containsKey(n)) { return cache.get(n); }
        else {
            Long fib = getSum(n-1) + getSum(n-2);
            cache.put(n, fib);
            return fib;
        }
    }
    
    0 讨论(0)
提交回复
热议问题