计算C中的嵌套根


9

我被要求仅使用递归来计算以下嵌套的根表达式。

在此处输入图片说明

我在下面编写了可行的代码,但他们只允许我们使用一个函数和1个输入n作为目的,而不是像我以前使用的2个。有人可以帮我将这段代码转换成一个可以计算表达式的函数吗?除了的功能,无法使用任何库<math.h>

n = 10的输出: 1.757932

double rec_sqrt_series(int n, int m) {
    if (n <= 0)
        return 0;
    if (m > n)
        return 0;
    return sqrt(m + rec_sqrt_series(n, m + 1));
}

double helper(int n) {
    return rec_sqrt_series(n, 1);
}

有人可以帮我将这段代码转换成一个可以计算表达式的函数吗?什么?帮助您摆脱helper
4386427

如果参数是错误的,我会叫abort()(从<stdlib.h>),而不是默默地返回0
卡兹

1
@chqrlieforyellowblockquotes已转贴。@pastaleg无用的递归如何?double nested_root(unsigned n) { double x = 0.0; if (n > 0) { x = nested_root(0); for (unsigned i = n; i > 0; i--) { x = sqrt(i + x); } } return x; }
chux-恢复莫妮卡

1
@ chux-ReinstateMonica:是的,更简单地滥用规则。
chqrlie

2
@Oppen:如果分配的目标是资助函数的非递归表达式,则可能不会要求使用“仅递归”来解决问题。当然,一个简单的循环可以轻松地计算出结果。尽管我通常会怀疑这些问题是在没有实际作业文本的情况下发布到Stack Overflow的。
埃里克·Postpischil

Answers:


7

使用的高位n作为计数器:

double rec_sqrt_series(int n)
{
    static const int R = 0x10000;
    return n/R < n%R ? sqrt(n/R+1 + rec_sqrt_series(n+R)) : 0;
}

自然,当初始n值等于R或大于此值时,该功能就会发生故障。这是一个更复杂的版本,适用于的任何正值n。有用:

  • n为负数时,它与上述版本类似,使用高位进行计数。
  • n为正数时(如果小于)R,则使用调用自身以-n如上所述评估函数。否则,它将自身R-1取反。这将评估该函数,就像使用调用该函数一样R-1。这会产生正确的结果,因为该序列仅在几十次迭代后就停止以浮点格式更改-较深数的平方根被稀释,因此没有作用。因此,对于n较小的阈值,该函数具有相同的值。
double rec_sqrt_series(int n)
{
    static const int R = 0x100;
    return
        0 < n ? n < R ? rec_sqrt_series(-n) : rec_sqrt_series(1-R)
              : n/R > n%R ? sqrt(-n/R+1 + rec_sqrt_series(n-R)) : 0;
}

好主意,但假定使用32位整数:)
chqrlie

1
@chqrlieforyellowblockquotes:不,这就是为什么R要分开的原因,因此可以对其进行调整。在n达到32 之前,对于IEEE-754 binary64,返回值停止更改,而在达到256之前,对于的合理格式,返回值停止更改double。因此,我正在考虑一种替代版本,该版本可以转换上面的钳位输入R,但是它需要使用符号位,并且我仍在研究中。
埃里克·Postpischil

还有其他的配对功能,您可以使用,但没有这么简单你的。它们的主要优点通常是可以任意精度工作,但是OP从来没有提到这一要求。
Ruud Helderman

@chqrlieforyellowblockquotes:完成。现在,n无论的宽度如何,对于任何正数都将产生正确的答案int
埃里克·Postpischil

5

如果不对公式进行数学转换(我不知道是否可能),就不能真正仅使用一个参数,因为每个元素都需要两个信息:当前步骤和原始信息n。但是你可以作弊。一种方法是对int参数中的两个数字进行编码(如Eric所示)。

另一种方法是将原始文件存储n在静态局部变量中。在第一个调用中,我们保存n在此静态变量中,我们开始递归,最后一步,将其重置为哨兵值:

// fn(i) = sqrt(n + 1 - i + fn(i - 1))
// fn(1) = sqrt(n)
//
// note: has global state
double f(int i)
{
    static const int sentinel = -1;
    static int n = sentinel;

    // outside call
    if (n == sentinel)
    {
        n = i;
        return f(n);
    }

    // last step
    if (i == 1)
    {
        double r = sqrt(n);
        n = sentinel;
        return r;
    }

    return sqrt(n + 1 - i + f(i - 1));
}

显然static int n = sentinel不是标准C语言,因为sentinel它不是C语言中的编译时间常数(这很奇怪,因为gcc和clang都不会抱怨,即使使用-pedantic

您可以改为:

enum Special { Sentinel = -1 };
static int n = Sentinel;

有趣的方法,但恐怕初始化程序static int n = sentinel;在C中并不完全符合要求,因为sentinel它不是C标准所定义的常量表达式。它可以在C ++中工作,并且可以在C模式下使用gcc和clang的当前版本进行编译,但不能在MSVC 2017中进行编译,但是您可能应该写这篇static int n = -1;
文章,

1
@chqrlieforyellowblockquotes ish。感谢您指出这一点。有趣的编译器行为。我已经在这个问题中问过这个问题:stackoverflow.com/q/61037093/2805305
bolov

5

这个问题就是扭曲的解决方案。

这是一个使用带有一个或两个int参数的单个函数的函数:

  • 如果第一个参数为正,则计算该值的表达式
  • 如果第一个参数为负,则必须在其后跟随第二个参数,并在计算中执行单个步骤,然后重复上一步。
  • 它使用<stdarg.h>可能允许或不允许的方式。

这是代码:

#include <math.h>
#include <stdarg.h>

double rec_sqrt_series(int n, ...) {
    if (n < 0) {
        va_arg ap;
        va_start(ap, n);
        int m = va_arg(ap, int);
        va_end(ap);
        if (m > -n) {
            return 0.0;
        } else {
            return sqrt(m + rec_sqrt_series(n, m + 1));
        }
    } else {
        return rec_sqrt_series(-n, 1);
    }
}

这是具有单个功能的另一种解决方案,仅使用<math.h>,但是以不同的方式滥用规则:使用宏。

#include <math.h>

#define rec_sqrt_series(n)  (rec_sqrt_series)(n, 1)
double (rec_sqrt_series)(int n, int m) {
    if (m > n) {
        return 0.0;
    } else {
        return sqrt(m + (rec_sqrt_series)(n, m + 1));
    }
}

还有另一种,严格来说是递归的,但是具有单个递归级别,没有其他技巧。正如Eric所评论的那样,它使用了一个for循环,该循环在OP的约束下可能无效:

double rec_sqrt_series(int n) {
    if (n > 0) {
        return rec_sqrt_series(-n);
    } else {
        double x = 0.0;
        for (int i = -n; i > 0; i--) {
            x = sqrt(i + x);
        }
        return x;
    }
}

是的,我猜这行得通。非常感谢您提供的所有帮助
Ronen Dvorkin

最后double rec_sqrt_series(int n),IMO通过将符号用作递归标记来满足OP的目标。(我丢弃else不需要作为returnif
chux -恢复莫妮卡

1
@ chux-ReinstateMonica:else当然可以删除,但是我有点喜欢if返回结果的两个分支的对称性,一种函数式编程风格。
chqrlie

@ chux-ReinstateMonica:我希望分配的“仅递归”要求排除了迭代。
埃里克·Postpischil

@EricPostpischil:是的,我也这么认为,但是没有得到OP的反馈。
chqrlie

0

这是另一种方法。

它依赖于int32位。这个想法是使用64位的高32位int

1)查看该呼叫是否是递归呼叫(或“外部”呼叫)

2)递归期间将目标值保存在高32位中

// Calling convention:
// when calling this function 'n' must be a positive 32 bit integer value
// If 'n' is zero or less than zero the function have undefined behavior
double rec_sqrt_series(uint64_t n)
{
  if ((n >> 32) == 0)
  {
    // Not called by a recursive call
    // so start the recursion
    return rec_sqrt_series((n << 32) + 1);
  }

  // Called by a recursive call

  uint64_t rn = n & 0xffffffffU;

  if (rn == (n >> 32)) return sqrt(rn);      // Done - target reached

  return sqrt (rn + rec_sqrt_series(n+1));   // Do the recursive call
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.