我计算非常大的斐波那契数模的算法太慢

Rei*_*ius 6 java algorithm

目标是计算F(n)模m(m等于10的幂5),其中n可能真的很大:最多10的幂18。

我的算法太慢了。

我的方法:计算并存储所有不超过m的斐波那契数,然后遍历该数组并对斐波那契数取模。

一旦找到pisano周期的长度,我就可以使用该长度来计算任何 F(n)

我的代码:

import java.math.BigInteger;
import java.util.*;

public class FibonacciAgain {


    private static ArrayList<BigInteger> calc_fib() {
        ArrayList<BigInteger> fib = new ArrayList<>();

        fib.add(BigInteger.ZERO);
        fib.add(BigInteger.ONE);
        for (int i = 2; i <= 100000; i++) {
            fib.add(fib.get(i - 2).add(fib.get(i - 1)));

        }
        return fib;
    }

    private static long calculatePeriod(ArrayList<BigInteger> fib, long modulo) {

        long periodLength = 0;
        boolean periodFound = false;
        long[] period = new long[1000000];
        period[0] = 0;
        period[1] = 1;
        period[2] = 1;


        int i = 3;
        while (!periodFound) {
            //period[i] = fib.get(i)
            //period[i]= fib.get(i).divide(new BigInteger(String.valueOf(i))).longValue();
            //System.out.println("Fib at " + i + ": " + fib.get(i));
            period[i] = fib.get(i).mod(new BigInteger(String.valueOf(modulo))).longValue();
            //System.out.println("1:" + period[i]);
            //System.out.println("2:" + period[i - 1]);
           // System.out.println("3: " + period[i - 2]);
            if (i == 100000){
                periodFound = true;
                periodLength = i - 1;

            }

           // if (period[i] == 1 && period[i - 1] == 1 && period[i - 2] == 0) {
            if (period[i - 1] == 1 && period[i - 2] == 0) {
                periodFound = true;
                periodLength = i - 2;

                //System.out.println("found");

            }
            i++;

        }
        //System.out.println("Period Length:" + periodLength);

        return periodLength;
    }


    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        long n = scanner.nextLong();
        long m = scanner.nextLong();


        //M Fibonacci Numbers up to are stored here
        ArrayList<BigInteger> fib = new ArrayList<>();
        fib = calc_fib();

        // get the length of the pisano period
        long periodLength = calculatePeriod(fib, m);

        //
        long fibFirst = n%periodLength;
        System.out.println(fib.get((int) fibFirst).mod(new BigInteger(String.valueOf(m))).longValue());

    }
}
Run Code Online (Sandbox Code Playgroud)

有什么建议,如何使其更快?

Spe*_*tre 5

无需使用,BigInteger因为:

1*2*3*4*...*N mod M
1+2+3+4+...+N mod M
Run Code Online (Sandbox Code Playgroud)

是相同的

(...(((1*2 mod M)*3 mod M)*4 mod M)...*N mod M)
(...(((1+2 mod M)+3 mod M)+4 mod M)...+N mod M)
Run Code Online (Sandbox Code Playgroud)

从(假设karatsuba乘法)O(3*N*(n^log2(3)))和/或加成O(N*n) 线性O(N),这应该n大大提高...乘数/加法器的比例位宽,并且恒定时间也要好得多...

IIRC还有快速斐波那契计算的公式(转换O(N)为近似值)O(log(N))

这里有几个例子:快速斐波那契算法

这是朴素()和快速(通过平方2x2矩阵使用幂)算法的C ++示例:modfib0modfib1

1*2*3*4*...*N mod M
1+2+3+4+...+N mod M
Run Code Online (Sandbox Code Playgroud)

注意要遵守您的约束,所使用的int变量必须至少为64位宽!我在旧的32位环境中,不想用bigint类破坏代码,所以我仅用以下方法进行了测试:

int x,m=30000,n=0x7FFFFFFF;
x=modfib0(n,m);
x=modfib1(n,m);
Run Code Online (Sandbox Code Playgroud)

结果如下:

[10725.614 ms] modfib0:17301 O(N)
[    0.002 ms] modfib1:17301 O(log2(N))
Run Code Online (Sandbox Code Playgroud)

如您所见,快速算法比线性算法快得多...但是,对于Windows环境,测量时间太短了,它的大部分时间很可能是开销,而不是函数本身,因此我认为它应该足够快对于n=10^18它的复杂性是O(log2(N))我的估计:

64-31 = 33 bits
0.002 ms * 33 = 0.066 ms
Run Code Online (Sandbox Code Playgroud)

因此64位计算应该0.1 ms在我的机器(AMD A8-5500 3.2 GHz)的执行时间以下完成,我认为这是可以接受的...

64位的线性算法如下所示:

10.725614 s * 2^33 = 865226435999039488 s = 27.417*10^9 years
Run Code Online (Sandbox Code Playgroud)

但是正如您所看到的,您早在那之前就已经染上了老年...