【问题标题】:Why does recursion work even if there's no base case?为什么即使没有基本情况,递归也能工作?
【发布时间】:2017-10-24 13:48:29
【问题描述】:

我正在学习递归,所以我正在尝试创建一个程序,以递归方式反转数组中的数字并使用分而治之技术(我不确定这是否是分而治之),所以我的问题是什么是,我想知道为什么如果我删除第 11 行程序仍然可以正常工作,但如果我删除第 16 行,它会无限运行。
我检查了我的调试器,我知道为什么它是无限循环,因为每个堆叠的帧索引左仍然小于右,这使得循环无限,所以我的问题,为什么递归调用从检查 while 条件开始并跳过该行11?这对我来说很奇怪,因为我只是学习了一些递归的基础知识,所以这对我来说有点困惑。所以在我的代码中,第 16 行被认为是基本情况?因为我从教程中了解到递归需要一个基本案例。

#include <stdio.h>
#define size 10
void swap(int *a, int *b)
{
    int temp= *b;
    *b = *a;
    *a = temp;
}
void revcur (int arr[], int left, int right)
{
    if (left>=right) return; //This program still works even if i delete this line or comment 
    while (left<right)
    {
        swap(&arr[left],&arr[right]);
        revcur(arr,left+1,right-1);
        return; //This program will go to infinite recursive if i delete this
    }
}
int main()
{
    int arr[size]={1,2,3,4,5,6,7,8,9,10};
    revcur(arr,0,size-1);
    int i; for (i=0; i<size; i++)
    {
        printf("%d ",arr[i]);
    }
}

【问题讨论】:

  • 我们在 SO 消息中没有行号。
  • 在 while 循环 while (left&lt;right) 内,您正在递增和递减变量 left 和 right 但没有分配新值。

标签: c recursion


【解决方案1】:

假设左=2,右=1。如果您将第 11 行留在其中,条件将为真,您将立即退出函数(返回)。当您删除第 11 行时,您将进入 while 循环。 while 语句检查 left 是否小于 right,事实并非如此。因此,它将跳过 while 循环中的所有代码,从而将您带到函数的末尾,因此它将自动返回。这与保留第 11 行的情况相同。

删除第 16 行时它无限循环的原因是因为您没有将“左”和“右”更新为新值。所以如果left确实小于right,它会进入循环,因为left和right的值永远不会改变,所以下次它到达“while”语句的那一行时,“left”仍然会小于“right” ",因此循环将无限期地继续。

【讨论】:

  • 我明白了,我还想问一件事,为什么每次程序进入递归时,它都会检查“while”语句,跳过“if”语句?递归没有从函数的开头开始循环?如果我上面的陈述是错误的,请纠正我
  • 仅仅是因为leftright 的值没有得到更新。你熟悉可重入代码和可重入吗?
【解决方案2】:

此代码模式:

while (condition)
{
    // do something
    return;
}

完全等价于

if (condition)
{
    // do something
    return;
}

换句话说,您的基本情况是!condition


你可以用

替换你的实现
void revcur(int arr[], int left, int right)
{
    if (left>=right) return;

    swap(&arr[left],&arr[right]);
    revcur(arr,left+1,right-1);
}

这将是完全等价的(while 从未真正循环过)。


您的版本似乎有迭代等价物的痕迹:

void revcur(int arr[], int left, int right)
{
    while (left<right) {
        swap(&arr[left++],&arr[right--]);
    }
}

【讨论】:

    【解决方案3】:

    你的函数过于复杂,其实就是这么干的:

    void revcur(int arr[], int left, int right)
    {
      if (left<right)
      {
        swap(&arr[left], &arr[right]);
        revcur(arr, left + 1, right - 1);
      }
    }
    

    原始函数中的 while 循环永远不会循环超过一次,因此具有 while 实际上毫无意义;这只是写if的一种奇怪的方式。

    考虑以下函数。

    void foo(int x)
    {
      printf("x = %d\n",x );
    
      if (x > 0)
        foo(x - 1);
    }
    
    
    void bar(int x)
    {
      printf("x = %d\n", x);
    
      while (x > 0)
      {
        bar(x - 1);
        return;
      }
    }
    

    foobar 做同样的事情,只是 bar 滥用 while 循环,将其用作 if,就像您在函数中所做的那样。

    现在尝试从bar 函数中删除return,看看会发生什么。

    【讨论】:

      【解决方案4】:

      你的函数是void。所以最后一次你在递归中调用它。它不会进入while循环,因此不会再次调用该函数,从而结束递归。而且因为 ypu 在你的 while 循环中有一个返回,我想你可以用 if 语句替换它,它基本上会做同样的事情,你可以摆脱 return

      【讨论】:

      • 如果我在递归下删除return;,为什么它会进入无限循环?
      • 因为它永远不会结束while的条件。
      • 函数无效与是否进入while循环有什么关系?
      • 尝试用if(left&lt;right) 替换你的声明while (left&lt;right) 并去掉return。之后你不应该有任何无限循环。原因是在 while 循环中,该函数将通过递归进入直到 left&lt;right。在最后一次调用时,它不会进入函数,但仍处于第一个 while 循环中,并且没有任何返回,它不会离开 while 循环。
      【解决方案5】:

      此声明

      if (left>=right) return; //This program still works even if i delete this line or comment 
      

      只是多余的。

      如果left &gt;= right为真,那么while语句left &lt; right的条件为假,while循环将不会被执行。

      所以你可以删除 if 语句。

      void revcur (int arr[], int left, int right)
      {
          while (left<right)
          {
              swap(&arr[left],&arr[right]);
              revcur(arr,left+1,right-1);
              return; //This program will go to infinite recursive if i delete this
          }
      }
      

      但是,您可能不会删除 return 语句,因为在这种情况下,循环将是无限的,因为在循环内部变量 leftright 本身的值没有改变,而 while 的条件如果最初评估为 true,则语句将始终评估为 true。

      另一方面,使用 while 语句是没有意义的,因为在任何情况下,由于 return 语句,假设循环体只会执行一次。重要的是 while 语句的条件。

      所以你可以用 while 语句代替 if 语句。

      例如

      void revcur (int arr[], int left, int right)
      {
          if ( left < right)
          {
              swap(&arr[left],&arr[right]);
              revcur(arr,left+1,right-1);
          }
      }
      

      考虑到如果数组只包含一个元素,那么它将与自身交换,即该函数将有一个多余的递归调用。

      该函数可以重写为只有两个参数,并且本身没有多余的递归调用。

      下面是一个演示程序,展示了如何使用两个参数编写函数。

      #include <stdio.h>
      
      void swap( int *a, int *b )
      {
          int temp= *b;
          *b = *a;
          *a = temp;
      }
      
      void reverse( int a[], size_t n )
      {
          if ( !( n < 2 ) )
          {
              swap( &a[0], &a[n-1] );
              reverse( a + 1, n - 2 );
          }
      }
      
      int main(void) 
      {
          int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
          const size_t N = sizeof( a ) / sizeof( *a );
      
          for ( size_t i = 0; i < N; i++) printf( "%d ", a[i] );
          putchar( '\n' );
      
          reverse( a, N );
      
          for ( size_t i = 0; i < N; i++) printf( "%d ", a[i] );
          putchar( '\n' );
      
          return 0;
      }
      

      程序输出是

      1 2 3 4 5 6 7 8 9 10 
      10 9 8 7 6 5 4 3 2 1 
      

      【讨论】:

      • 它是如何工作的?这对我来说很有趣。我想不通,为什么即使n-2 仍然有效,我的意思是,当正确的索引变为 8 6 4 2 0 时,交换怎么能不跳过某些索引?
      • @owenizedd 函数首先检查数组是否至少有两个元素。例如,如果数组只有一个元素,那么就没有什么可以交换的了。如果有超过或等于 2 个元素,则交换第一个和最后一个元素,即交换 2 个元素,并调用包含没有这两个元素的元素的数组的函数。
      猜你喜欢
      • 2020-03-07
      • 2021-05-13
      • 1970-01-01
      • 2018-01-10
      • 2016-09-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-17
      相关资源
      最近更新 更多