【问题标题】:Recursive algorithm for cos taylor series expansion c++cos taylor级数展开c ++的递归算法
【发布时间】:2014-12-01 17:54:08
【问题描述】:

我最近写了一个计算机科学考试,他们要求我们给出 cos taylor 级数展开的递归定义。这是系列

cos(x) = 1 - x^2/2! + x^4/4! + x^6/6! ...

函数签名如下

float cos(int n , float x)

其中 n 表示用户想要计算的序列中的数字,x 表示 cos 函数中 x 的值

我显然没有正确回答这个问题,过去几天我一直在试图弄清楚,但我遇到了难题

有人能帮我从某个地方开始吗?

【问题讨论】:

  • 如果必须有递归定义,则将1 - x^2/2! + x^4/4! - x^6/6! + ...重写为1 - (x^2/2)(1 - (x^2/(3*4))(1 - (x^2/(5*6))(1 - ...)))
  • 我建议您不要使用递归来评估泰勒级数。大多数程序都被赋予了有限的递归空间。您可能会用尽限制;问题是针对哪个限制。
  • @ThomasMatthews 如果他用尽了堆栈限制,我会感到非常惊讶;无需评估该系列的很多成员即可获得准确的结果。 (另一方面,这里真的没有理由使用递归;一个简单的循环也一样好,而且可能更快。)

标签: c++ math recursion trigonometry


【解决方案1】:

到目前为止,所有答案每次都重新计算阶乘。我肯定不会那样做的。相反,你可以写:

float cos(int n, float x)
{
    if (n > MAX)
         return 1;
    return 1 - x*x / ((2 * n - 1) * (2 * n)) * cos(n + 1, x);
}

考虑 cos 返回以下内容(对于点的位置感到抱歉):

您可以看到,对于 n>MAX、n=MAX 等,这是正确的。 x 的符号交替和幂很容易看到。

最后,在 n=1 时,你得到 0! = 1,因此调用 cos(1, x) 可以获得 cos 的泰勒展开式的第一个 MAX 项。

通过开发(当它有几个术语时更容易看到),您可以看到第一个公式等效于以下内容:

对于 n > 0,您在 cos(n-1, x) 中将前一个结果除以 (2n-3)(2n-2),然后乘以 x²。可以看到当n=MAX+1这个公式为1,n=MAX则为1-x²/((2MAX-1)2MAX),以此类推。

如果您允许自己使用辅助函数,那么您应该将上面的签名更改为float cos_helper(int n, float x, int MAX) 并像这样调用它:

float cos(int n, float x) { return cos_helper(1, x, n); }


编辑:将n 的含义从评估项的程度(如目前为止的答案中)反转为项数(如问题中及以下),但仍不会每次都重新计算总阶乘,我建议使用二项关系。

让我们简单地定义cos(0,x) = 0cos(1,x) = 1,并尝试一般地实现泰勒级数前n项之和的cos(n,x)。

那么对于每个 n > 0,我们可以写出 cos(n,x) from cos(n-1,x) :

cos(n,x) = cos(n-1,x) + x2n / (2n)!

现在对于 n > 1,我们尝试让 cos(n-1,x) 的最后一项出现(因为它是最接近我们要添加的一项):

cos(n,x) = cos(n-1,x) + x² / ((2n-1)2n) * ( x2n-2 / (2n-2)!)

通过将此公式与前一个公式相结合(将其调整为 n-1 而不是 n):

cos(n,x) = cos(n-1,x) + x² / ((2n-1)2n) * ( cos(n-1,x) - cos(n-2,x) )

我们现在有了 cos(n,x) 的纯递归定义,没有辅助函数,没有重新计算阶乘,并且 n 是泰勒分解之和中的项数。

但是,我必须强调以下代码的性能非常糟糕:

  • 性能方面,除非某些优化允许不重新评估在上一步中评估为 cos( (n-1) - 1, x)cos(n-1,x)
  • 精度方面,因为抵消效应:我们得到 x2n-2 / (2n-2) 的精度!很糟糕

现在这个免责声明已经到位,代码来了:

float cos(int n, float x)
{
    if (n < 2)
        return n;
    float c = x * x / (2 * (n - 1) * 2 * n);
    return (1-c) * cos(n-1, x) + c * cos(n-2, x);
}

【讨论】:

  • 这是正确的方向,但从问题来看,“n 表示用户想要计算的系列中的数字”。
  • @Cheersandhth.-Alf 发现得很好。我更新了答案以考虑到这一点。它现在应该完全按照问题中的描述做,但我不相信它的结果。
  • 只需对第一种方法(及其内容)使用辅助函数 cos_helper(m,n,x) 其中 m=MAX 和 cos(n,x) {return cos_helper(n, 1,x);}
  • 好吧,如果您可以使用辅助函数,那么您无论如何都可以做任何事情,不是吗?我不确定这个问题的限制究竟是什么。 @KeatonPennels ?
【解决方案2】:
cos(x)=1 - x^2/2! + x^4/4! - x^6/6! + x^8/8!.....
=1-x^2/2 (1 - x^2/3*4 + x^4/3*4*5*6 -x^6/3*4*5*6*7*8)
=1 - x^2/2 {1- x^2/3*4 (1- x^2/5*6 + x^4/5*6*7*8)}
=1 - x^2/2 [1- x^2/3*4 {1- x^2/5*6 ( 1- x^2/7*8)}]

    double cos_series_recursion(double x, int n, double r=1){

        if(n>0){

            r=1-((x*x*r)/(n*(n-1)));

            return cos_series_recursion(x,n-2,r);
         }else return r;
    }

【讨论】:

  • 我同意静态在这里不是一个好的选择。我现在把它改成了一个可选参数。
【解决方案3】:

一种利用静态变量的简单方法:

double cos(double x, int n) {
    static double p = 1, f = 1;
    double r;

    if(n == 0)
        return 1;

    r = cos(x, n-1);
    p = (p*x)*x;
    f = f*(2*n-1)*2*n;

    if(n%2==0) {
        return r+p/f;
    } else {
        return r-p/f;
    }
}

请注意,我在运算中乘以 2*n 以获得下一个阶乘。 让n 与我们需要的阶乘对齐,这可以通过两个操作轻松完成:f = f * (n - 1) 然后f = f * n

when n = 1, we need 2!  
when n = 2, we need 4!  
when n = 3, we need 6!  

所以我们可以安全地翻倍 n 并从那里开始工作。我们可以写:
n = 2*n;
f = f*(n-1);
f = f*n;

如果我们这样做,我们需要将偶数/奇数检查更新为 if((n/2)%2==0),因为我们将 n 的值加倍。

这可以写成f = f*(2*n-1)*2*n;,现在我们在检查它是偶数/奇数时不必划分n,因为n没有被改变。

【讨论】:

    【解决方案4】:

    您可以使用循环或递归,但我建议使用循环。无论如何,如果你必须使用递归,你可以使用类似下面的代码

    #include <iostream>
    
    using namespace std;
    
    int fact(int n) {
        if (n <= 1) return 1;
        else return n*fact(n-1);
    }
    
    float Cos(int n, float x) {
        if (n == 0) return 1;
        return Cos(n-1, x) + (n%2 ? -1 : 1) * pow (x, 2*n) / (fact(2*n));
    }
    
    int main()
    {
       cout << Cos(6, 3.14/6);
    
    }
    

    【讨论】:

    • 他不必对阶乘部分使用递归函数,只需 cos taylor 级数部分。您可以对阶乘使用循环,这样会更明智。
    【解决方案5】:

    就像求和一样。

    float cos(int n , float x) 中的参数nl,现在就这样做... 一些伪代码:

    float cos(int n , float x)
    {
                    //the sum-part
        float sum = pow(-1, n) * (pow(x, 2*n))/faculty(2*n);
    
        if(n <= /*Some predefined maximum*/)
            return sum + cos(n + 1, x);
        return sum;
    }
    

    【讨论】:

    • 递归测试不应该是if ( n &gt;= 0 )吗?
    • “测试”是什么意思?中止条件?不。看看总和,它上升到无穷大,因为我们真的负担不起,我们会在达到某个指定深度后退出。
    • 这是正确的方向,但从问题来看,“n 表示用户想要计算的系列中的数字”
    【解决方案6】:

    当您想要递归但函数参数没有携带您需要的信息时,通常的技术是引入 帮助函数 来进行递归。

    我的印象是,在 Lisp 世界中,约定是将这样的函数命名为 something-auxauxiliary 的缩写),但这可能有过去只是一个有限的群体。

    无论如何,这里的主要问题是n 代表递归的自然结束点,即基本情况,然后您还需要一些本身可以工作到n 的索引。因此,这是辅助函数的额外参数的一个很好的候选者。另一位候选人源于考虑该系列的一个术语与前一个术语的关系。

    【讨论】:

    • 您可以通过倒计时来完成,但这意味着每次都要重新计算x^(2*n)(2*n)!。否则,您可能至少需要三个附加值:与n 进行比较的计数器、x 的幂的运行值以及阶乘的运行值。 (我可能会为此创建一个类。)
    • @JamesKanze:我认为你只需要尾递归的运行值。 OP没有对这种要求说任何话。但我同意,这是最好的。 :)
    • 好点。我不知道为什么我没有想到它;我过去使用过非尾递归,所以我知道这种可能性存在。
    猜你喜欢
    • 2019-08-18
    • 1970-01-01
    • 2020-04-06
    • 2011-09-12
    • 2020-11-09
    • 1970-01-01
    • 2018-03-25
    • 2015-12-16
    • 1970-01-01
    相关资源
    最近更新 更多