【问题标题】:How can I make my implementation of Project Euler 25 faster, so I can actually compute the answer?我怎样才能使我的 Project Euler 25 的实施更快,这样我才能真正计算出答案?
【发布时间】:2011-07-11 05:05:31
【问题描述】:

这是我对Problem 25 - Project Euler 的实现(请参阅代码中的 cmets 以了解其工作原理):

#include <iostream> //Declare headers and use correct namespace
#include <math.h>

using namespace std;

//Variables for the equation F_n(newTerm) = F_n-1(prevTerm) + Fn_2(currentTerm)
unsigned long long newTerm = 0;
unsigned long long prevTerm = 1; //F_1 initially = 1
unsigned long long currentTerm = 1; //F_2 initially = 2

unsigned long long termNo = 2; //Current number for the term

void getNextTerms() { //Iterates through the Fib sequence, by changing the global variables.
    newTerm = prevTerm + currentTerm; //First run: newTerm = 2
    unsigned long long temp = currentTerm; //temp = 1
    currentTerm = newTerm; //currentTerm = 2
    prevTerm = temp; //prevTerm = 1
    termNo++; //termNo = 3
}

unsigned long long getLength(unsigned long long number) //Returns the length of the number
{
    unsigned long long length = 0;
    while (number >= 1) {
        number = number / 10;
        length++;
    }
    return length;
}

int main (int argc, const char * argv[])
{
    while (true) {
        getNextTerms(); //Gets next term in the Fib sequence
        if (getLength(currentTerm) < 1000) { //Checks if the next terms size is less than the desired length
        }
        else { //Otherwise if it is perfect print out the term.
            cout << termNo;
            break;
        }
    }
}

这适用于示例,只要这一行就可以快速运行:

        if (getLength(currentTerm) < 1000) { //Checks if the next term's size is less than the desired length

说 20 或更低,而不是 1000。但是如果这个数字大于 20,则需要很长时间,我的耐心变得更好,我停止了程序,我怎样才能使这个算法更有效?

如果您有任何问题,请在 cmets 中提问。

【问题讨论】:

  • 128 位 unsigned long long 的最大值类似于 3*10^38。这太小了,无法容纳千位数字。
  • @Mat:您对此有何建议?
  • 一般来说,long long 将是 64 位 - 这种类型(如果它是无符号的)可以表示的最大数字是 18446744073709551615,它有 20 位数字。没有办法用这种类型来表示一个有 1000 位数字的数字(这就是为什么它永远占用你的程序的原因——它无法完成)。要查找 1000 位的斐波那契数,您将无法仅使用 long long 类型 - 您需要以其他方式表示数字,
  • 欧拉项目的目标是在寻找解决方案的同时获得乐趣,寻求帮助只会破坏乐趣恕我直言,对于您的问题,快速的方法是使用原生处理大量数字的python跨度>

标签: c++ performance


【解决方案1】:

斐波那契数(以及任何线性循环序列)有一个封闭公式。

所以F_n = C1 * a^n + C2 * b^n,其中 C1、C2、a 和 b 是可以从初始条件中找到的数字,即对于 Fib 情况,来自

F_n+2 = F_n+1 + F_n

F_1 = 1

F_2 = 1

我在这里不是故意给出他们的价值观。这只是一个提示。

【讨论】:

  • 只是为了让人们知道:我决定移植到 Python。并对有助于提高效率的答案投了赞成票,并接受了这个答案,因为它对我帮助最大。
【解决方案2】:

第n个fibonacci number是=

(g1^n-g2^n)/sqrt(5). 
where g1 = (1+sqrt(5))/2 = 1.61803399
      g2 = (1-sqrt(5))/2 = -0.61803399

为了找到第n个斐波那契数的长度,我们可以计算log(第n个斐波那契数)。所以,第n个斐波那契数的长度是,

 log((g1^n-g2^n)/sqrt(5)) = log(g1^n-g2^n)-0.5*log(5).
 you can just ignore g2^n, since it is very small negative number.

因此,第n个斐波那契的长度是

n*log(g1)-0.5*log(5)

我们需要找到 'n' 的最小值,使得这个长度 = 1000,所以我们可以找到长度刚好大于 999 的 n 的值。

所以,

n*log(g1)-0.5*log(5) > 999
n*log(g1) > 999+0.5*log(5)
n > (999+0.5*log(5))/log(g1)
n > (999.3494850021680094)/(0.20898764058551)
n > 4781.859263075

因此,所需的最小n是4782。不使用任何编码,最简单的方法。

注意:所有地方都使用以 10 为底的日志。

【讨论】:

  • 上述方法的说明给here
【解决方案3】:

这可能会加快速度:

int getLength(unsigned long long number) //Returns the length of the number when expressed in base-10
{
    return (int)log10(number) + 1;
}

...但是,您无法使用unsigned long long 达到 1000 位数字。我建议研究任意精度算术库,或内置任意精度算术的语言。

【讨论】:

    【解决方案4】:

    您可以尝试使用矩阵求幂来计算斐波那契数。然后重复加倍以得到一个超过 1000 位的数字,并在该范围内使用二进制搜索来找到第一个数字。

    【讨论】:

      【解决方案5】:

      使用双打,您可以知道最高指数为 308 的解决方案:

      得到 exp 250 的序列,然后将两个数字除以 1e250。用这两个数字重新启动算法

      如果你这样做 4 次,你会得到正确的答案

      【讨论】:

        【解决方案6】:

        C++代码可能如下:

        #include "iostream"
        #include "string.h"
        #include "algorithm"
        using namespace std;
        
        string addTwoString(string a, string b)
        {
            if (a.length() == 0)
            {
                return b;
            }
        
            if (b.length() == 0)
            {
                return a;
            }
            reverse(a.begin(), a.end());
            reverse(b.begin(), b.end());
            string result = "";
            string str_1, str_2;
            if (a.length() > b.length())
            {
                str_1 = b;
                str_2 = a;
            }
            else
            {
                str_1 = a;
                str_2 = b;
            }
            int index = 0;
            int value = 0, over_value = 0;
            for (; index < str_1.length(); ++index)
            {
                int temp_1 = (int)(str_1[index] - '0');
                int temp_2 = (int)(str_2[index] - '0');
                int temp = temp_1 + temp_2 + over_value;
                value = temp % 10; 
                over_value = temp / 10; 
                char c = (char)(value + '0');
                result += c;
            }
            for (; index < str_2.length(); ++index)
            {
                int temp_2 = (int)(str_2[index] - '0');
                int temp = temp_2 + over_value;
                value = temp % 10;
                over_value = temp / 10;
                char c = (char)(value + '0');
                result += c;
            }
        
            if (over_value > 0)
            {
                char c = (char)(over_value + '0');
                result += c;
            }
            reverse(result.begin(), result.end());
            return result;
        }
        
        int main()
        {
            string a = "1";
            string b = "1";
            string c = addTwoString(a, b);
            int index = 3;
            while (c.length() < 1000)
            {
                a = b;
                b = c;
                c = addTwoString(a, b);
                ++ index;
            }
            cout << index << endl;
        }
        

        【讨论】:

        • 请简单说明一下您解决提问者问题的方法。
        • 与其反转两个输入字符串的副本,不如仅在输入上向后迭代会更加更有效率。或者甚至只是一直以相反的顺序存储你的字符串。并将它们设为uint8_t,而不是字符,因此您无需在每一步都转换为/从 ASCII 转换。基本上,您应该将 little-endian BCD 存储在 uint8_t 的向量中,每个字节有一个十进制数字。这消除了所有的反转,以及 '0' 的加/减
        • 你应该添加一个解释,这只是使用 ASCII 字符串的 BigNum 实现。
        【解决方案7】:

        我只是使用了一个垂直添加数组的递归函数来完成这个问题。基本上零运行时间,不到 50 行代码。享受:

        #include <stdio.h>
        
        int Calc_Fib (int numA[], int numB[], int temp[], int index) {
            int i = 0;
        
            //Check 1000th digit for non-zero value.
            if (numB[999] != 0) return index;
        
            //Add arrays A and B vertically.
            for (i = 0; i < 1000; ++i)   {
                temp[i] += (numA[i] + numB[i]);
        
                if (temp[i] > 9)   {
                    temp[i + 1] = temp[i] / 10;
                    temp[i] %= 10;
                }
        
                numA[i] = numB[i];
                numB[i] = temp[i];
                temp[i] = 0;
            }
            Calc_Fib(numA, numB, temp, ++index);
        }
        
        int main()  {
            int numA[1000];   //Holds previous term.
            int numB[1000];   //Holds current term.
            int temp[1000];   //Holds temporary number for vertical addition.
            int i        = 0;
            int indexVal = 2;
        
            for (i = 0; i < 1000; ++i)  {
                numA[i] = 0;
                numB[i] = 0;
                temp[i] = 0;
            }
        
            //Initialize first two terms.
            numA[0] = (numB[0] = 1);
        
            indexVal = Calc_Fib(numA, numB, temp, indexVal);
        
            printf("Tada: %d\n", indexVal);
        
            return 0;
        }
        

        【讨论】:

        • 当您最多只存储每个元素中两位小数的总和时,您不需要 int 数组。 uint8_t 会起作用,就像@June 的回答一样。另外,为什么temp 需要一个完整的数组?用一个额外的局部变量来实现进位肯定不是那么难吗?
        • 您对临时数组的看法是正确的,我当时绝对没有想到这一点。但是,我对'uint8_t'并不熟悉,因为我对代码很陌生。我也不确定 June 的字符串添加功能是如何工作的。
        猜你喜欢
        • 2018-09-28
        • 1970-01-01
        • 1970-01-01
        • 2017-12-20
        • 2023-03-18
        • 1970-01-01
        • 1970-01-01
        • 2011-09-27
        • 2020-04-06
        相关资源
        最近更新 更多