带有记忆的斐波那契序列仍然比正常的递归解决方案慢

Fibonacci sequence with memoization still slower than normal recursive solution

我注意到我的带记忆的递归斐波那契算法只适用于大于 12 的值。

我将它与其他人的方法实现进行了比较,他也用它传递了一个数组。我以为每次传递一个数组,在重新调用方法时会占用太多内存,从而使程序变慢,但事实并非如此。

不知何故,我仍然无法理解我的外部数组为何不比正常的递归斐波那契算法快。我想这可能是因为我的 if 中的许多条件,我检查它是否较慢但我不确定。

如果可能的话,你能看看我的代码并告诉我我做错了什么或者后台发生了什么吗?

public class Fibonacci2 {
    int memory[];

    public Fibonacci2(int f) {  
        if (memory == null) {
            memory = new int[f+1];

            memory[0] = 0;
            memory[1] = 1;
            memory[2] = 1;
        }   
    }
    
    public int recursive(int f) {
        if (memory[f-1] != 0 && memory[f-2] != 0 && f>2) {
            memory[f] = memory[f-1] + memory[f-2];
        } else if (memory[f-1] == 0 && memory[f-2] != 0 && f>2) {
            memory[f] = recursive(f-1) + memory[f-2];
        } else if (f>2) {
            memory[f] = recursive(f-1) + recursive(f-2);
        }

        return memory[f];
    }
    
}

public class Fibonacci1 {
    public Fibonacci1() {}
    
    public int recursive(int f) {
        if (f > 2) {
            return recursive(f-1) + recursive(f-2);
        } else {
            return 1;
        }
    }
}

public class Main {
    public static void main(String[] args) {
        int fibo = 12;

        Fibonacci1 fiboEx1 = new Fibonacci1();
        Fibonacci2 fiboEx2 = new Fibonacci2(fibo);

        int a = fiboEx1.recursive(fibo);

        long start = System.nanoTime();
        long stop = System.nanoTime();

        System.out.println("fibo of " + fibo + " is " + a);
        System.out.println("Fibonacci time without memorization: " + (stop-start));

        int b = fiboEx2.recursive(fibo);

        start = System.nanoTime();
        stop = System.nanoTime();

        System.out.println("fibo of " + fibo + " is " + b);
        System.out.println("Fibonacci time with memorization: " + (stop-start));
    }
}

I noticed that my recursive Fibonacci algorithm with memorization only works for values bigger than 12.

我猜你的意思是写“值 小于 比 12”。无论哪种方式,这都是不正确的:您的解决方案适用于大于 12 的值,直到结果类型溢出。

也就是说,您的代码 存在多个问题。

  1. 不要在构造函数中测试 if(memory==null)总是正确。

  2. memory[f-1] != 0 && memory[f-2] != 0 && f>2 — 你为什么要在这里测试 f > 2?只有在开始时测试它以避免访问负数数组元素时,该测试才有意义。否则测试是多余的。

  3. 整个功能比必要的更复杂。分离访问记忆值的逻辑,使其更清晰、更简单、更不易出错:

    public int recursive(int f){
        if (f > 2 && memory[f - 2] == 0) {
            memory[f - 2] = recursive(f - 2);
        }
        if (f > 1 && memory[f - 1] == 0) {
            memory[f - 1] = recursive(f - 1);
        }
        memory[f] = memory[f - 2] + memory[f - 1];
        return memory[f];
    }
    

    ......但是你当然可以而且应该进一步简化它:

    public int recursive(int f){
        if (memory[f] == 0) {
            memory[f] = recursive(f - 2) + recursive(f - 1);
        }
        return memory[f];
    }
    

    这也是一样的。但不是每个函数调用都对 memoised 值进行多次冗余检查,它只是处理 它自己的 值(即 f 的值),并为其余的调用自身。

  4. 从根本上说,将其作为带有构造函数和附加函数的 class 毫无意义:该函数只能用于获取单个值的斐波那契数,这,此外,在构造函数和函数调用中需要相同(这很容易出错!)。例如,以下会使代码崩溃:new Fibonacci2(10).recursive(15)。这样做也是如此:new Fibonacci2(1)。好的代码不会允许这样的错误发生。

这是一个没有这些问题的解决方案:

class Fib {
    static int memory[];

    private static void resizeMemory(int newSize, int[] oldValues) {
        if (newSize < 3) newSize = 3;
        memory = new int[newSize];
        System.arraycopy(oldValues, 0, memory, 0, oldValues.length);
    }

    public static int fib(int n) {
        if (memory == null || memory.length <= n) {
            resizeMemory(n + 1, new int[] {0, 1, 1});
        }

        if (n == 0) return 0;
        if (memory[n] == 0) memory[n] = fib(n - 2) + fib(n - 1);
        return memory[n];
    }
}

但我仍然不会像这样实际编写一个“真正的”斐波那契实现——维护一个全局内存缓存会引入复杂性并且没有任何实际用途。这是一个不使用缓存的高效实现。实际上,如果您计算 很多 的斐波那契数,它的效率只会降低,即便如此,这也不太可能成为瓶颈。

为了说明,这是它的样子:

private static int fibImpl(int n, int a, int b) {
    if (n == 0) return a;
    if (n == 1) return b;
    return fibImpl(n - 1, b, a + b);
}

public static int fib(int n) {
    return fibImpl(n, 0, 1);
}