更有效的解决方案:项目欧拉#2:甚至斐波那契数字

时间:2014-06-30 05:02:10

标签: java algorithm

问题:

  

Fibonacci序列中的每个新术语都是通过添加    前两个任期。

     

从1和2开始,前10个术语将    是:

     

1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...

     

考虑Fibonacci序列中的值,而不是    超过四百万,找到偶数值的总和。

我的代码:(工作正常)

public static void main(String[] agrs){
    int prevFirst=0;
    int prevSecond=1;
    int bound=4_000_000;
    int evenSum=0;

    boolean exceed=false; //when fib numbers > bound
    while(!exceed){
        int newFib=prevFirst + prevSecond;
        prevFirst = prevSecond;
        prevSecond = newFib;

        if(newFib > bound){
            exceed=true;
            break;
        }

        if(newFib % 2 == 0){
            evenSum += newFib;
        }
    }

    System.out.println(evenSum);

}

我正在寻找一种更有效的算法来解决这个问题。任何提示?

7 个答案:

答案 0 :(得分:13)

考虑以下规则时:

  

甚至+偶数=偶数

     

偶数+奇数=奇数

     

奇数+偶数=奇数

     

奇数+奇数=偶数

第一个斐波纳契数的奇偶性是:

  

o o o o o o e ... ...

因此,基本上,你只需要做三步即可。这是:

(1,1,2)
(3,5,8)
(13,21,34)

鉴于(a,b,c),这是(b+c,b+2*c,2*b+3*c)

这意味着我们只需要存储最后两个数字,并计算给定的(a,b)(a+2*b,2*a+3*b)

因此(1,2) -> (5,8) -> (21,34) -> ...并始终返回最后一个。

这比“过滤器” - 方法更快,因为它使用了减少流水线操作的if语句。


结果代码是:

int b = 1;
int c = 2, d;
long sum = 0;
while(c < 4000000) {
    sum += c;
    d = b+(c<<0x01);
    c = d+b+c;
    b = d;
}
System.out.println(sum);

jdoodle(基准测试,冷启动需要5微秒,平均50纳秒,基于1M次的平均值)。当然,循环中的指令数量更大。但循环重复了三分之一。

答案 1 :(得分:3)

你无法改善它,你所做的任何改进都可以忽略不计,并且依赖于你正在运行的操作系统。

示例:
在我的Mac上循环运行代码 1M次 73-75ms(运行几次)。
改变条件:

if(newFib % 2 == 0){

为:

if((newFib & 1) == 0){

再次运行几次我得到了51-54ms。

  • 如果你在不同的操作系统上运行相同的东西(和 可能会得到不同的结果。
  • 即使我们将上述内容视为改进,在1M中划分〜20ms,单次运行所获得的“改进”也毫无意义(约20纳米)。

答案 2 :(得分:3)

假设连续的斐波纳契数

a, b,
c =  a +  b,
d =  a + 2b,
e = 2a + 3b,
f = 3a + 5b,
g = 5a + 8b = a + 4(a + 2b) = a + 4d,

使用
它似乎更有效 ef 0 = 0,ef 1 = 2,ef n = ef n-2 + 4 ef N-1

答案 3 :(得分:1)

正如我在评论中提到的,实际上没有必要进一步改进。 我做了一些测量

  • 循环1000000次整个事情
  • 以[ms]
  • 衡量时间
  • ms / 1000000 = ns
  • 因此单次通过时间[ns]为:

    1. [176 ns] - 利用偶数数字每三分之一
    2. [179 ns] - &amp; 1代替%2
    3. [169 ns] - &amp; 1代替%2,如果通过 - ,^,&amp;
      ,则消除 [edit1]新代码
    4. [105 ns] - 利用偶数是每三分之一+衍生的斐波那契的双重迭代 [edit2]新代码
    5. [76 ns] - 减少操作数计数以降低开销和堆垃圾
  • 最后一个明显胜过我的机器(虽然我希望第一个会是最好的)

  • 全部在Win7 x64 AMD A8-5500 3.2GHz上进行了测试
  • 没有线程的应用程序32位编译器BDS2006 Trubo C ++

  • 1,2在答案中已经很好地提到了,所以我只评论3:

    s+=a&(-((a^1)&1));
    
  • (a ^ 1)否定最爱情

  • ((a ^ 1)&amp; 1)为1表示偶数,0表示奇数 a
  • - ((a ^ 1)&amp; 1))为偶数为-1,奇数为 a 为0
  • -1是0xFFFFFFFF,因此它的数字不会改变它
  • 0是0x00000000,因此它的数量将为0
  • 因此无需 if
  • 也可以代替 xor (^)使用否定(!),但在我的机器上慢得多

好的,这里是代码(如果你想自己编码,请不要进一步阅读):

//---------------------------------------------------------------------------
int euler002()
    {
    // Each new term in the Fibonacci sequence is generated by adding the previous two terms.
    // By starting with 1 and 2, the first 10 terms will be: 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...
    // By considering the terms in the Fibonacci sequence whose values do not exceed four million, find the sum of the even-valued terms.
    int a,a0=0,a1=1,s=0,N=4000000;
/*
    //1. [176 ns]
    a=a0+a1; a0=a1; a1=a;   // odd
    a=a0+a1; a0=a1; a1=a;   // even
    for (;a<N;)
        {
        s+=a;
        a=a0+a1; a0=a1; a1=a;   // odd
        a=a0+a1; a0=a1; a1=a;   // odd
        a=a0+a1; a0=a1; a1=a;   // even
        }

    //2. [179 ns]
    for (;;)
        {
        a=a0+a1; a0=a1; a1=a;
        if (a>=N) break;
        if ((a&1)==0) s+=a;
        }
    //3. [169 ns]
    for (;;)
        {
        a=a0+a1; a0=a1; a1=a;
        if (a>=N) break;
        s+=a&(-((a^1)&1));
        }
    //4. [105 ns] // [edit1]
    a0+=a1; a1+=a0; a=a1;       // 2x
    for (;a<N;)
        {
        s+=a; a0+=a1; a1+=a0;   // 2x
        a=a0+a1; a0=a1; a1=a;   // 1x
        }
*/
    //5. [76 ns] //[ edit2]
    a0+=a1; a1+=a0;             // 2x
    for (;a1<N;)
        {
        s+=a1; a0+=a1; a1+=a0;  // 2x
        a=a0; a0=a1; a1+=a;     // 1x
        }

    return s;
    }
//---------------------------------------------------------------------------

[edit1]加快代码添加

  • CommuSoft建议每次迭代纤维蛋白重复超过1个数字,以最大限度地减少操作。
  • 好主意,但他的评论中的代码没有给出正确的答案
  • 我调整了一点我的,所以结果如下:
  • [105 ns] - 利用偶数是每三分之一+衍生的斐波纳契的双重迭代
  • 这几乎是从中得出的加速比的两倍
  • 在代码中查找[edit1]或查找// 4。

[edit2]更快的代码添加 - 只需重新排序一些变量和操作使用以获得更快的速度 - [76 ns]减少了操作数,降低了开销和堆垃圾

答案 4 :(得分:1)

如果你查看Fibonacci系列,对于偶数2 8 34 144 610,你会发现偶数之间存在奇妙的关系,例如:

34 = 4*8 + 2,
144 = 34*4 + 8,
610 = 144*4 + 34;

这意味着即使在Fibonacci中,下一个也可以表示如下

Even(n)=4*Even(n-1)+E(n-2);
Java中的

public static void main(String[] args) {
    Scanner in = new Scanner(System.in);
    int t = in.nextInt();
    for(int a0 = 0; a0 < t; a0++){
        long n = in.nextLong();
        long a=2;
        long b=8;
        long c=0;
        long sum=10;
        while(b<n)
        {
            sum +=c;
            c=b*4+a;
            a=b;
            b=c;     
        }
        System.out.println(sum);
    }
}

答案 5 :(得分:0)

项目Euler问题2的答案是(在Java中):

int x = 0;
int y = 1;
int z = x + y;
int sumeven = 0;
while(z < 4000000){
    x = y;
    y = z;
    z = x + y;
    if(z % 2 == 0){
        sumeven += z; /// OR sumeven = sumeven + z
    }
}
System.out.printf("sum of the even-valued terms: %d \n", sumeven);

这是最简单的答案。

答案 6 :(得分:0)

F(n)是第n个Fibonnaci数,即F(n)= F(n-1)+ F(n-2)
让我们说F(n)是偶数,然后是 F(n)= F(n-1)+ F(n-2)= F(n-2)+ F(n-3)+ F(n-2)
F(n)= 2F(n-2)+ F(n-3)
- 这证明了每三个项都是偶数(如果F(n-3)是偶数,那么F(n)必须是偶数)
F(n)= 2 [F(n-3)+ F(n-4)] + F(n-3)
= 3F(n-3)+ 2F(n-4)
= 3F(n-3)+ 2F(n-5)+ 2F(n-6)

从等式1:
F(n-3)= 2F(n-5)+ F(n-6)
2F(n-5)= F(n-3)-F(n-6)

F(n)= 3F(n-3)+ [F(n-3)-F(n-6)] + 2F(n-6)
= 4F(n-3)+ F(n-6)

如果偶数序列由每三个数字(n,n-3,n-6,...)组成
甚至Fibonacci序列:

E(k)= 4E(k-1)+ E(k-2)


Fib序列F = {0,1,1,2,3,5,8 .....}
甚至Fib序列E = {0,2,8,.....} CODE:

 public static long findEvenFibSum(long n){
     long term1=0;
     long term2=2;
     long curr=0;
     long sum=term1+term2;          
        while((curr=(4*term2+term1))<=n){
            sum+=curr;
            term1=term2;
            term2=curr;             
        }
  return sum;
  }