是否有正确的解决方案?

时间:2010-03-02 17:52:45

标签: php fibonacci

在解决了一段时间以来我一直被束缚的编程挑战之后,我总是在想,“它有效,那就足够了”。

我认为这不是正确的心态,在我看来,我认为我应该始终尝试以最佳的性能进行编码。

无论如何,说到这里,我只是试了一个ProjectEuler问题。具体问题#2。

我怎样才能改进此解决方案。我觉得它的真的详细。就像我在递归中传递前一个数字一样。

<?php
  /* 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, ...

     Find the sum of all the even-valued terms in the sequence which do not exceed
     four million.
   */
   function fibonacci ( $number, $previous = 1 ) {
     global $answer;
     $fibonacci = $number + $previous;
     if($fibonacci > 4000000) return;
     if($fibonacci % 2 == 0) {
       $answer = is_numeric($answer) ? $answer + $fibonacci : $fibonacci;
     }
     return fibonacci($fibonacci, $number);
   }
   fibonacci(1);
   echo $answer;
?>

请注意,这不是作业。几百年前我离开了学校。我只是感到无聊并且经历了项目欧拉问题

8 个答案:

答案 0 :(得分:7)

  

解决后我总是在想   我有一个编程挑战   被捆绑了一段时间,“它   工作,那太好了“。

     

我认为这不是真的   在我看来,我是正确的心态   我想我应该一直努力   代码性能最佳。

Code Complete中提出的一个经典事物是,程序员在给定目标的情况下,可以使用众多指标之一创建“最佳”计算机程序,但无法针对所有一次参数。参数如

  • Code Readabilty
  • 代码输出的可理解性
  • 代码长度(行)
  • 执行代码的速度(性能)
  • 编写代码的速度

随意对这些参数中的任何一个进行优化,但请记住,同时优化所有这些参数可能会令人沮丧,或导致系统过度设计。

你应该问自己:你的目标是什么?在这种情况下,什么是“足够好”?如果你只是在学习并想要让事情更加优化,那么一定要去做,只要意识到一个完美的程序需要无限的时间来构建,而时间本身就是有价值的。

答案 1 :(得分:2)

你可以通过三次操作来避免mod 2部分(每三个元素是偶数),所以它读取:      $ fibonacci = 3 * $ number + 2 * $ previous; 而斐波那契的新输入是($ fibonnacci,2 * $ number + $ previous) 我不熟悉php,所以这只是一般的算法建议,我不知道它是否是正确的语法。它实际上是相同的操作,它只是用几个乘法代替模数和加法。

另外,请确保以$ even开头,$ previous作为序列中前面的奇数(你可以从$ number开始为2,$ previous为1,并且总和也开始在2)。

答案 2 :(得分:1)

忘记斐波纳契(问题2),我说只是在欧拉进步。不要浪费时间为每个问题找到最佳代码。

如果您的答案达到一分钟规则,那么您可以尝试下一个。在遇到一些问题之后,事情将变得更加困难,并且您将在编写代码时优化代码以实现该目标

答案 3 :(得分:0)

其他人也在这里说过“这是问题的一部分,例如问题与真正的<强>商业问题”

由于多种原因,这个问题的答案很难回答:

  • 语言起着重要作用。有些语言更适合某些问题,所以如果你遇到不匹配,你会发现你的解决方案“不那么雄辩”
  • 这取决于您需要多长时间来解决问题,解决问题的时间越多,您就越有可能找到您喜欢的解决方案(尽管反过来偶尔也是如此,因为太多的时间让您在思考)
  • 这取决于您的整体满意度。我曾经参与过几个项目,我认为这些项目很棒,编码很好,而其他部分都是完全垃圾,但它们超出了我有时间处理的范围。

我想最重要的是,如果你认为它是一个很好的解决方案,而你的客户/购买者/团队/等同意那么它是一个很好的解决方案。你可能会在未来改变主意,但现在它是一个很好的解决方案。

答案 4 :(得分:0)

我实际上没有对此进行测试...但是在我称之为“完成”之前,我个人本可以尝试在此解决方案中解决此问题。

通过使用sum参数实现递归来尽可能地避免全局变量

编辑:根据nnythm的算法推荐更新(很酷!)

function fibonacci ( $number, $previous, $sum ) {
    if($fibonacci > 4000000) { return $sum; }
    else {
        $fibonacci = 3*$number + 2*$previous;
        return fibonacci($fibonnacci,2*$number+$previous,$sum+$fibonacci); 
    }
}
echo fibonacci(2,1,2);

答案 5 :(得分:0)

使用指南来解决问题的代码执行时间不应超过一分钟。这对欧拉问题最重要,IMO。

除此之外,只需确保它的可读性 - 确保您可以轻松查看代码的工作原理。通过这种方式,您可以更轻松地了解如果遇到类似您解决的Euler问题之一的问题,这样可以更快地解决问题 - 因为您已经知道应该如何解决它。

你可以为自己设定其他标准,但我认为这超出了欧拉问题的意图 - 对我而言,问题的背景似乎更适合关注效率和可读性而不是其他任何事情

答案 6 :(得分:0)

[耸肩]

应根据要求评估解决方案。如果满足所有要求,那么其他一切都是满满的。如果满足所有要求,并且您个人对解决方案不满意,则可能需要重新评估要求。这就是你可以采取这个元物理问题,因为我们开始涉及项目管理和业务:S

嗯,关于你的Euler-Project问题,只有我的两便士:

  1. 考虑重构迭代,而不是递归
  2. 注意系列中的每三个学期都是均匀的?一旦你获得了开始学期,就不需要模数
  3. 例如

    public const ulong TermLimit = 4000000;
    
    public static ulong CalculateSumOfEvenTermsTo (ulong termLimit)
    {
        // sum!
        ulong sum = 0;
    
        // initial conditions
        ulong prevTerm = 1;
        ulong currTerm = 1;
        ulong swapTerm = 0;
    
        // unroll first even term, [odd + odd = even]
        swapTerm = currTerm + prevTerm;
        prevTerm = currTerm;
        currTerm = swapTerm;
    
        // begin iterative sum,
        for (; currTerm < termLimit;)
        {
            // we have ensured currTerm is even,
            // and loop condition ensures it is 
            // less than limit
            sum += currTerm;
    
            // next odd term, [odd + even = odd]
            swapTerm = currTerm + prevTerm;
            prevTerm = currTerm;
            currTerm = swapTerm;
    
            // next odd term, [even + odd = odd]
            swapTerm = currTerm + prevTerm;
            prevTerm = currTerm;
            currTerm = swapTerm;
    
            // next even term, [odd + odd = even]
            swapTerm = currTerm + prevTerm;
            prevTerm = currTerm;
            currTerm = swapTerm;
        }
        return sum;
    }
    

    所以,也许更多的代码行,但[实际上]保证更快。迭代方法不像“优雅”,但保存递归方法调用并节省堆栈空间。其次,展开术语生成[即显式扩展循环]减少了您必须执行模数运算和测试“偶数”条件的次数。扩展还减少了评估结束条件[如果当前术语小于限制]的次数。

    它是“更好”,不,它只是“另一种”解决方案。

    为C#道歉,不熟悉php,但我相信你可以很好地翻译它。

    希望这会有所帮助,:))

    干杯

答案 7 :(得分:0)

完全是您的选择,您是否对解决方案感到满意,或者您是否希望进一步改进。有很多项目欧拉问题,蛮力解决方案需要太长时间,而且你必须寻找一个聪明的算法。

问题2不需要任何优化。您的解决方案已经足够快了。 还是让我解释一下什么样的优化是可能的。通常有助于对该主题进行一些研究。例如。 Fibonacci numbers上的维基页面包含此公式

  

fib(n)=(phi ^ n - (1-phi)^ n)/ sqrt(5)

其中phi是黄金比例。即。

  

phi =(sqrt(5)+1)/ 2。

如果你使用fib(n)大约是phi ^ n / sqrt(5)那么你可以找到小于M的最大Fibonacci数的索引

  

n = floor(log(M * sqrt(5))/ log(phi))。

E.g。对于M = 4000000,我们得到n = 33,因此fib(33)是最小的斐波纳契数小于4000000.可以观察到,即使n是3的倍数,fib(n)也是偶数。因此,偶数斐波那契的总和数字是

  

fib(0)+ fib(3)+ fib(6)+ ... + fib(3k)

要查找已关闭的表单,我们使用维基百科页面中的上述公式并注意到 总和基本上只有两个geometric series。数学并非完全无足轻重,但使用这些想法可以证明

  

fib(0)+ fib(3)+ fib(6)+ ... + fib(3k)=(fib(3k + 2)-1)/ 2.

由于fib(n)的大小为O(n),因此直接解决方案的复杂度为O(n ^ 2)。 使用上面的闭合公式和快速方法来评估斐波纳契数 具有O(n log(n)^(1 + epsilon))的复杂度。对于小数字,任何解决方案当然都可以。