【问题标题】:Can this implementation of Ackermann function be called tail recursive?阿克曼函数的这种实现可以称为尾递归吗?
【发布时间】:2015-10-19 14:42:46
【问题描述】:

我用 C 编写了以下代码。我们可以称之为尾递归实现吗?

#include <stdio.h>

int ackermann(unsigned int *m, unsigned int *n, unsigned int* a, int* len)
{
  if(!*m && *len == -1) {
    return ++*n;
  }
  else if(!*m && *len >= 0) {
    ++*n;
    *m = a[(*len)--];
  }
  else if(*n == 0) {
    --*m;
    *n = 1;
  } else {
    ++*len;
    a[*len] = *m - 1;
    --*n;
  }
  return ackermann(m, n, a, len);
}

int main()
{
  unsigned int m=4, n=1;
  unsigned int a[66000];
  int len = -1;

  for (m = 0; m <= 4; m++)
    for (n = 0; n < 6 - m; n++) {
      unsigned int i = m;
      unsigned int j = n;
      printf("A(%d, %d) = %d\n", m, n, ackermann(&i, &j, a, &len));
    }

  return 0;
}

如果它不是尾递归的,请建议如何做到这一点。在 C/C++/Java 或非函数式编程语言中,任何对 Ackermann 尾递归版本的引用都会很好。

【问题讨论】:

  • 如果解决了您的问题,请将对应的答案标记为solution,这样以后的读者可以很容易地找到解决方案。

标签: c memory recursion tail-recursion ackermann


【解决方案1】:

您的函数使用数据结构进行回溯,因此虽然它是尾递归函数,但它绝对不是一个简单的递归或迭代过程。数组a 充当递归堆栈的角色。你可以一起写出递归调用:

int ackermann(unsigned int *m, unsigned int *n, unsigned int* a, int* len)
{
  while (*m || *len != -1) {
      if(!*m && *len >= 0) {
        *n++;
        *m = a[(*len)--];
      } else if(*n == 0) {
        *m--;
        *n = 1;
      } else {
        ++*len;
        a[*len] = *m - 1;
        *n--;
      }
  }
  return ++*n;
}

仍然没有任何递归调用,我认为这是一个递归过程。

【讨论】:

  • every recursion can be converted into iteration 通过模拟递归堆栈(通常,在堆上 - 问题变成了所需的此空间的大小)。 -- unsigned int a[66000]; 看起来可以用来模拟递归栈。当然,对于更大的nm,如果它适当的阿克曼函数,则保留空间必须变得更大。
  • 并且使用的空间不断增长,对应于完成计算所需的时间。我认为,这正是这个功能的意义所在——它的增长非常非常非常快/大。正如对该问题的评论所说,转换是关于“将堆栈变量转换为(模拟的)变量堆栈”。 :)
  • @WillNess 您在谈论递归函数与迭代函数,而我在谈论递归过程与迭代过程。如果您创建一个迭代循环并扩展一个数据结构来进行某种回溯,那么它仍然是一个递归过程。同样,如果您在 TCO 语言中使用尾递归,即使它使用递归也是一个迭代过程。可以迭代的递归,比如斐波那契,变成了迭代的。阿克曼函数不会。
  • Tail recursion == iterative process 为假。尾递归只是意味着代码相当于一个循环;它不排除在循环中改变商店的代码。尾递归是一种句法特征。那里没有回溯,数据存储被填满。 Ackermann 函数的要点是,这家商店的增长速度比指数法则 AFAIR 快得多。无论是隐式堆栈还是显式堆栈,都没有关系。是的,我们可以称它为“递归过程”,但问题是它是“尾递归”。
  • @WillNess 为什么我选择该数组是因为超出 A(4, 1) 它太大而无法由 C 数据类型保存。 A(4,1) 的值是 65k+,所以我知道不会有更多的调用。虽然,较小的数组也可以工作,是的,它已像堆栈一样使用。如果我们需要更大的值,动态堆栈会更好。
【解决方案2】:

definition 你的ackermann 函数尾递归函数,因为你直接返回递归案例的结果。由于没有进一步的逻辑取决于递归调用的结果,因此编译器可以安全地应用尾递归优化。

【讨论】:

  • 感谢您的确认。
  • 没问题 :) 感谢您利用此优化。由于递归调用而创建的额外堆栈帧非常昂贵。
猜你喜欢
  • 2012-01-31
  • 1970-01-01
  • 2015-03-04
  • 2012-12-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-28
  • 1970-01-01
相关资源
最近更新 更多