是什么定义了递归函数?

时间:2015-08-19 19:32:42

标签: c++ c recursion

除了提出的简单问题here 并基于this评论

问题是解决方案在什么时候停止被认为是递归的,即使实现的基本算法是递归的?

为了完整起见,所有情况都使用以下功能:

int counter=0;
int reps=0;

void show(int x)
{
#ifdef OUTPUT
    printf("==============>>> %d <<<\n", x);
#endif
    counter+=x;
    ++reps;
}

int bit_val(unsigned int v)
{
  static const int MultiplyDeBruijnBitPosition2[32] =
  {
    0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,
    31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
  };
  return MultiplyDeBruijnBitPosition2[(unsigned int)(v * 0x077CB531U) >> 27];
}

案例1:清除递归

void uniq_digitsR(int places, int prefix, int used) {
  if (places==1) {
    show(prefix*10+bit_val(~used));
    return;
  }
  int base=prefix*10;
  unsigned int unused=~used;
  while(unused) {
    unsigned int diff=unused & (unused-1);
    unsigned int bit=unused-diff;
    unused=diff;
    uniq_digitsR(places-1, base+bit_val(bit), used|bit);
  }
}

int uniq_digits9() {
  unsigned int used=~((1<<10)-1); // set all bits except 0-9
  used |= 1;                      // unset 0
  uniq_digitsR(9, 0, used);
  return 0;
}

案例2:硬编码展开

请注意,函数在任何时候都不会调用自身或任何直接或间接调用者

void uniq_digits1(int prefix, unsigned int used) {
  show(prefix*10+bit_val(~used));
}

void uniq_digits2(int prefix, unsigned int used) {
  int base=prefix*10;
  unsigned int unused=~used;
  while (unused) {
    unsigned int diff=unused & (unused-1);
    unsigned int bit=unused-diff;
    unused=diff;
    uniq_digits1(base+bit_val(bit), used|bit);
  }
}

void uniq_digits3(int prefix, unsigned int used) {
  int base=prefix*10;
  unsigned int unused=~used;
  while (unused) {
    unsigned int diff=unused & (unused-1);
    unsigned int bit=unused-diff;
    unused=diff;
    uniq_digits2(base+bit_val(bit), used|bit);
  }
}

void uniq_digits4(int prefix, unsigned int used) {
  int base=prefix*10;
  unsigned int unused=~used;
  while (unused) {
    unsigned int diff=unused & (unused-1);
    unsigned int bit=unused-diff;
    unused=diff;
    uniq_digits3(base+bit_val(bit), used|bit);
  }
}

void uniq_digits5(int prefix, unsigned int used) {
  int base=prefix*10;
  unsigned int unused=~used;
  while (unused) {
    unsigned int diff=unused & (unused-1);
    unsigned int bit=unused-diff;
    unused=diff;
    uniq_digits4(base+bit_val(bit), used|bit);
  }
}

void uniq_digits6(int prefix, unsigned int used) {
  int base=prefix*10;
  unsigned int unused=~used;
  while (unused) {
    unsigned int diff=unused & (unused-1);
    unsigned int bit=unused-diff;
    unused=diff;
    uniq_digits5(base+bit_val(bit), used|bit);
  }
}

void uniq_digits7(int prefix, unsigned int used) {
  int base=prefix*10;
  unsigned int unused=~used;
  while (unused) {
    unsigned int diff=unused & (unused-1);
    unsigned int bit=unused-diff;
    unused=diff;
    uniq_digits6(base+bit_val(bit), used|bit);
  }
}

void uniq_digits8(int prefix, unsigned int used) {
  int base=prefix*10;
  unsigned int unused=~used;
  while (unused) {
    unsigned int diff=unused & (unused-1);
    unsigned int bit=unused-diff;
    unused=diff;
    uniq_digits7(base+bit_val(bit), used|bit);
  }
}

void uniq_digits9() {
  unsigned int used=~((1<<10)-1); // set all bits except 0-9
  used |= 1;                      // unset 0
  for (int i = 1; i < 10; i++) {
    unsigned int bit=1<<i;
    uniq_digits8(i,used|bit);
  }
}

案例3:迭代版

请注意,没有调用任何函数(除了明显显示),但它是相同的算法

void uniq_digits(const int array[], const int length) {
  unsigned int unused[length-1];                    // unused prior
  unsigned int combos[length-1];                    // digits untried
  int digit[length];                                // printable digit
  int mult[length];                                 // faster calcs
  mult[length-1]=1;                                 // start at 1
  for (int i = length-2; i >= 0; --i)
     mult[i]=mult[i+1]*10;                          // store multiplier
  unused[0]=combos[0]=((1<<(length))-1);            // set all bits 0-length
  int depth=0;                                      // start at top
  digit[0]=0;                                       // start at 0
  while(1) {
    if (combos[depth]) {                            // if bits left
      unsigned int avail=combos[depth];             // save old
      combos[depth]=avail & (avail-1);              // remove lowest bit
      unsigned int bit=avail-combos[depth];         // get lowest bit
      digit[depth+1]=digit[depth]+mult[depth]*array[bit_val(bit)]; // get associated digit
      unsigned int rest=unused[depth]&(~bit);       // all remaining
      depth++;                                      // go to next digit
      if (depth!=length-1) {                        // not at bottom
        unused[depth]=combos[depth]=rest;           // try remaining
      } else {
        show(digit[depth]+array[bit_val(rest)]);    // print it
        depth--;                                    // stay on same level
      }
    } else {
      depth--;                                      // go back up a level
      if (depth < 0)
        break;                                      // all done
    }
  }
}

那么,只是CASE 1递归吗?或者我们还包括CASE 2甚至CASE 3

4 个答案:

答案 0 :(得分:10)

函数的递归定义与其递归实现(或算法)之间存在差异。

函数可以以递归方式在数学上定义,但是计算该函数的算法(即,实现)可能是非递归的,反之亦然。

请注意,同一功能可能有不同的数学定义和不同的算法。

在您提供的示例中, CASE 1-implementation 是递归的,而 CASE 2 - 非常明显CASE 3-implementation 不是递归的,无论函数的数学定义是否是递归的。

P.S。为了使它保持在问题的范围内,我故意没有触及直接/间接递归,也没有一些仅通过递归表达迭代的纯函数式语言。

答案 1 :(得分:2)

解决方案在任何时候都不会递归,对于任何输入,激活链中是否会出现多个函数实例:没有重新输入函数。

“unrolled recursion”是否递归?这取决于我们是否在讨论展开的解决方案所依据的概念或其实现。

显然,实现不是递归的。

显然,该函数的展开副本是基于递归实现的机械重复,并且该解决方案仍然表达该解决方案的某些方面;当您查看代码时,很明显如何将其回滚到递归实现中。我们还可以针对算法的递归描述验证该解决方案;也就是说,使用递归算法的描述作为我们的指南,我们可以很容易地说服自己展开的实现是对还是错。

很明显,展开的代码就是:递归解决方案的展开实现。我们不能否认与递归规范的联系,但我们必须承认递归没有发生。

答案 2 :(得分:1)

只是为了扩展@AlexShesterov给出的答案。显式递归通常很昂贵,并且根据用例的不同,存在溢出堆栈的风险,这就是为什么在实践中通常会通过类似的转换来避免它。

考虑一个尾递归函数。一个尾递归函数,就像听起来一样,是一个函数,它不会在函数调用中保留任何状态(递归调用在最后)。编译器通常会将这些实现为迭代代码,因为它是一个微不足道的转换。

递归是描述函数的一种非常简洁的方法,这就是使用它来描述很多内容的原因(即Fibonacci数)。然而,在实践中,通常更好的是迭代地重新制定它们。

斐波那契数字你有...

F(0) = 0
F(1) = 1
F(N) = F(N-1) + F(N-2)

因此,通常在C ++中,可以使用...

递归实现
size_t fib(size_t n) {

  if (n == 0)
    return 0;

  if (n == 1)
    return 1;

   return fib(n - 1) + fib(n - 2);
}

虽然,它效率低下(重新计算值,在调用堆栈上存储数据等等)。一个优化将 memoizing 函数调用。但是,实际值可以自下而上构建,因此最好只是迭代地重新制定它。

size_t fib(size_t n) {

  size_t p = 0, c = 1;

  if (n == 0)
    return 0;

   while (n--) {

     size_t t = c;

     c = p + c;
     p = t;
   }

  return c;
}

为了好玩,这在haskell中也是一样......

fib n = fibs !! n
  where fibs = 0 : 1 : zipWith (+) fibs (tail fibs)

答案 3 :(得分:1)

单词递归用于不同的上下文中。你有一个递归函数的简单定义,其中一个函数直接间接地调用自己,但在计算机科学中你也说一些关于一个过程。

迭代过程基本上是一个原始的递归函数。它是可以通过迭代或尾递归定义的所有函数。例子:

int test (int x)
{
  return x + 1;
}

int factorial(int x)
{
   int a = 1;
   while( x > 1 ) {
       a *= x;
   }
   return a;
}

int factorial_aux(int x, int a)
{
   return x == 0 ? a : factorial_aux(x-1, a*x);
}

递归过程是一个过程,它总是需要某种数据结构来保存数据,以便在跟踪时进行处理。一个例子可能是树遍历:

Node* search(Node* tree, int needle) {
  if( tree == NULL  || tree->value == needle )
    return tree;
  else
    return search(tree->left, needle) || search(tree->right, needle);
}

Node* search(Node* tree, int needle) {
  Stack<Node *> stack;
  if( tree )
    stack.push(tree);

  while ( stack.empty() == false ) {
      Node* n = stack.pop();
      if( n->value == needle )
          return n;

      if( tree->right != NULL )
         stack.push(tree->right);
      if( tree->left != NULL )
         stack.push(tree->left);
  }
  return NULL;
}

以上两者都是递归过程。使用递归函数而另一个使用循环的迭代函数的事实不会改变系统堆栈或显式堆栈随着进程从根进一步访问节点而增长的事实。

CPU通常不支持大多数这些东西,因此编译器需要通过使用原始指令并推送和清理堆栈以进行参数传递来进行大量“仿真”。因此,在引擎盖下,递归函数通常是具有不断增长的堆栈的迭代过程。

所以回答你的问题:

  • 对于某个流程:当使用的空间不变时
  • 对于某项功能:当无法进行直接或间接自助通话时