避免Fibonacci数列溢出的替代方法

3

我是Stackoverflow的新手,请指出任何可以提高问题质量的地方。

我的代码(或者希望完成的目标)是计算大规模斐波那契数在一个相当巨大的m下的模。为了使算法更加高效,我使用了pisano periods。实质上,我计算了m的pisano period,然后通过使用以下关系使余数的计算更加简单:

n个斐波那契数(模m的余数)的余数等于第k个斐波那契数(模m的余数),其中k=n%ppm的pisano period。

为了计算pisano period,我使用以下属性:

如果当前的Fib % m = 0并且迄今为止所有Fib的和% m = 0,则当前Fib的索引是m的pisano period。(注意索引必须大于0)

但是我在这个努力中遇到了问题:为了计算pisano period,我必须计算连续的斐波那契数。当需要计算的斐波那契数的数量变得非常大时,比如100,000,则数据类型long会溢出。

据我所知,任何计算pisano periods的努力都将需要计算fibonacci,因此唯一的解决方案似乎是用其他东西替换long。如果有人对可能的替代品有任何建议,我将不胜感激。

import java.util.*;

public class FibHuge {
    public static void main (String [] args) {
        Scanner in = new Scanner (System.in);
        long num = in.nextLong ();
        long mod = in.nextLong();

        System.out.println ( getMod(num, mod));
    }

    private static int getMod (long num, long mod) {
        Period per = new Period();

        long period = per.getPeriod (mod);
        int newFibNum = (int)(num % period);

        num = (num % mod);

        Integer ia[] = new Integer [per.al.size()];
        ia = per.al.toArray (ia);

        return ia[newFibNum];
    }
}

class Period {

    ArrayList <Long> al;
    long FNum;
    long SNum;

    Period () {
        al = new ArrayList <Long> ();
        FNum = 0;
        SNum = 1;
    }

    private long getFib (long first, long second){
        return first + second;
    }

    long getPeriod (long mod){
        boolean bool = true;
        long fibcount = 0;

        long currentmod = 0;
        long fib = 0;
        long sum = 0;

        while (bool){
            if (fibcount <= 1){
                currentmod = fibcount % mod;

                al.add (currentmod);

                sum += fibcount;
            }

            else {
                fib = getFib (FNum, SNum);
                FNum = SNum;
                SNum = fib;

                currentmod = (fib % mod);
                al.add (currentmod);

                sum += fib;
            }

            if ( (currentmod == 0 & (sum % mod) == 0) & fibcount > 0){
                return fibcount;
            }
            fibcount++;
        }

        return mod; //essentially just to satisfy the return condition
    }
}

当需要计算的斐波那契数列数量变得非常大时,比如100,000个,问题就出现了。我认为你对斐波那契数列有误解。它们的增长速度呈指数级别增长——在第93个斐波那契数列中,long类型的范围已经被超越了。 - Leon
@Leon 哦,那比我想象的要糟糕得多。尽管我知道它们是呈指数增长的,但我从未想过时间会过得这么快。 - Airdish
2个回答

5
使用 BigInteger,但请注意它速度较慢,但可以处理无限大的数字。

有多慢?该问题要求最大运行时间为1.5秒。 - Airdish
3
如果不知道处理器、时钟速度以及需要执行多少计算,1.5秒的时间就毫无意义。 BigInteger 可能可以正常工作,但你可能需要亲自尝试才能确定。 - Jim Garrison
4
“多慢?”- 试一试就知道。 - Stephen C
1
注意:除非模数非常大(太大而无法在内存中存储所有结果),否则您实际上不需要使用BigInteger来解决此问题。请参考我的答案。 - Peter Lawrey

3

除非你的模数太大以至于无法适应long,否则不需要使用BigInteger,如果是这种情况,我怀疑你在尝试找到解决方案时会耗尽内存。

不要计算第n个斐波那契数,然后执行模数,而是可以使用此属性在模数后计算第n个斐波那契数:

(a + b) % n = (a % n + b % n) % n;

换句话说,在每次迭代中,您只需要将数的模数相加即可。您可以将所有模数值保存在一个Set中,当您获得重复的结果时,就有了一个周期。您可以将迭代次数与结果一起存储,并使用此来计算周期。
实际上,模数有点昂贵,但由于您只会对小于2 *模数的数字进行求和,因此您可以简单地执行。
long c = a + b; // Fibonacci
if (c >= modulus) c -= modulus; // the only real change you need for modulus.

由于Java使用条件移动而不是实际分支,因此比使用%快得多。

如果不为您编写代码,则不需要更多详细信息。


我喜欢这个想法,但不幸的是,除非我为识别周期中的重复性产生一个新属性,也就是斐波那契数之和 % m,否则我无法使用它。这是我的代码的一个重要部分,因为它是指示序列重复的两个事物之一。 - Airdish
@Airdish 这就是为什么你需要一个值到最后出现的计数的映射表。一旦你得到了重复的数字,你就知道了周期。 - Peter Lawrey
1
抱歉,我不确定我理解了。在句号实际重复之前,序列中经常出现重复项。唯一的识别模式是每次迭代都以0、1开头。 - Airdish
@Airdish 不要考虑Set/Map的想法。只需运行您的算法,利用Peter提出的模算术属性。即,在循环中不要跟踪fibsum,而是跟踪fib%modsum%mod - Leon
@Airdish 如果你知道要查找的模式,就不需要记住先前的值。 - Peter Lawrey
1
非常感谢Peter Lawrey和@Leon提供的想法。我的程序现在真的很快。 :) - Airdish

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接