【问题标题】:How does this recursive function work in C++?这个递归函数在 C++ 中是如何工作的?
【发布时间】:2021-05-03 09:06:00
【问题描述】:

我目前正在上 C++ 课程,我们正在学习递归,在课堂上我的教授使用此函数作为递归示例,该函数旨在返回数字中的最小数字,并且是:

int smallD(int n) {
    if (n < 10) return n;
    int x = smallD(n / 10);
    if (x < n % 10) return x;
    else return n % 10;
}

我对将 x 设置为递归调用的工作原理感到困惑,该函数是否会一直运行 n / 10 直到 n

【问题讨论】:

  • "在 n
  • 递归函数的工作方式与非递归函数完全相同,因此它的工作方式与调用不同函数的方式完全相同。递归调用不会使当前函数调用从头或循环或类似的东西重新开始。
  • 是的,它应该是这样工作的。拿一张纸,一支笔,试着用手重现这个函数的结果。
  • 作为练习,您可以编写一个函数smallID3,它返回一个 3 位数字的最小数字。它不使用递归,而是调用smallID2(x/10),一个返回2位数字的最小数字的函数,然后调用smallD1,一个返回一位数字的最小数字的函数。您将意识到这三个都可以使用与您现在的代码非常相似的代码,并且只需重构即可再次获得递归函数

标签: c++ recursion


【解决方案1】:

这里有一些有助于理解递归的东西。添加打印语句以观察代码,因为它递归调用自身并传递和“缩进级别”以提供帮助。

获取原始的缩小代码并将其扩展为更具可读性的内容,并为其添加额外的调试信息。

int smallD(int n, const std::string& indent) {

    cout << indent << "enter: smallD(n=" << n << ")" << endl;

    if (n < 10)  {
        cout << indent << "n < 10 => returning: " << n << endl;
        return n;
    }

    cout << indent << "about to recurse inovking smallD(" << n / 10 << ")" << endl;
    int x = smallD(n / 10, indent+"  "); // grow the indent by 2 spaces
    cout << indent << "return from recursion, result is: " << x << endl;

    cout << indent << "x=" << x << "  n=" << n << " n%10=" << n % 10 << endl;

    if (x < n % 10) {
        cout << indent << "x is less than n%10, returning: " << x << endl;
        return x;
    }

    cout << indent << "x is greater than or equal n%10, returning: " << n%10 << endl;
    return n % 10;
}

让我们通过调用smallD(8942468, "")来尝试一下

enter: smallD(n=8942468)
about to recurse inovking smallD(894246)
  enter: smallD(n=894246)
  about to recurse inovking smallD(89424)
    enter: smallD(n=89424)
    about to recurse inovking smallD(8942)
      enter: smallD(n=8942)
      about to recurse inovking smallD(894)
        enter: smallD(n=894)
        about to recurse inovking smallD(89)
          enter: smallD(n=89)
          about to recurse inovking smallD(8)
            enter: smallD(n=8)
            n < 10 => returning: 8
          return from recursion, result is: 8
          x=8  n=89 n%10=9
          x is less than n%10, returning: 8
        return from recursion, result is: 8
        x=8  n=894 n%10=4
        x is greater than or equal n%10, returning: 4
      return from recursion, result is: 4
      x=4  n=8942 n%10=2
      x is greater than or equal n%10, returning: 2
    return from recursion, result is: 2
    x=2  n=89424 n%10=4
    x is less than n%10, returning: 2
  return from recursion, result is: 2
  x=2  n=894246 n%10=6
  x is less than n%10, returning: 2
return from recursion, result is: 2
x=2  n=8942468 n%10=8
x is less than n%10, returning: 2    // <== this is the final result

希望这能帮助您了解递归的工作原理。

【讨论】:

    【解决方案2】:

    递归函数的工作方式与非递归函数完全相同。
    一个常见的错误是尝试一次考虑所有递归调用,就好像它们具有共享状态一样,但理解递归函数的一个关键因素实际上是忽略递归而只是“局部思考”。

    也许通过一个例子可以澄清事情。
    让我们看一下smallD(321),将函数体中的n 替换为其值。

    smallD(321)
    
        if (321 < 10) return 321;
        int x = smallD(321 / 10);
        if (x < 321 % 10) return x;
        else return 321 % 10;
    

    第一个条件显然为假,为了确定x,我们需要smallD(321/10),即smallD(32)

    smallD(32)
    
        if (32 < 10) return 32;
        int x = smallD(32 / 10);
        if (x < 32 % 10) return x;
        else return 32 % 10;
    

    第一个条件再次为假,所以我们继续使用smallD(32/10)

    smallD(3)
    
        if (3 < 10) return 3;
        int x = smallD(3 / 10);
        if (x < 3 % 10) return x;
        else return 3 % 10;
    

    现在第一个条件为真,所以这里的结果显然是3
    现在我们可以返回并在每个等待的调用中使用x 的值。

    smallD(32)
    
        ...
        if (3 < 32 % 10) return 3;
        else return 32 % 10;
    

    3 &lt; 32 % 10 为假,所以我们将32 % 10 - 2 返回给调用者。

    smallD(321)
    
        ...
        if (2 < 321 % 10) return 2;
        else return 321 % 10;
    

    2 &lt; 321 % 10 为假,所以我们返回321 % 10,即1

    【讨论】:

    • 很好的解释。
    【解决方案3】:

    您的直觉并没有完全偏离:该函数确实“在 n 小于 10 之前一直运行 n/10” - 但它在对同一函数的不同 调用 中。

    您的程序保留了一堆函数调用。您调用的每个函数都会在当前堆栈中的所有内容之上放置一个(所谓的)“框架”。在那个框架内“活”了所有“属于”该函数调用的变量。当函数退出时,它会从堆栈中删除自己的帧。当前正在执行的函数的框架位于栈顶。那么,让我们看看如果你调用smallD(123)会发生什么:

    • 你从堆栈上的其他东西开始,至少是你的main()。您对smallD(123) 的调用将smallD(123) 的框架置于堆栈顶部。此帧包含变量n = 123。让我们将此帧堆栈称为A

    • 由于n &gt;= 10,您的smallD(123) 调用smallD(123 / 10),即smallD(12)(整数除法基本上在C++ 中截断)。因此,您将另一个框架放在堆栈顶部。此堆栈帧对应于smallD(12),并包含变量n = 12。让我们将此堆栈帧称为B

    • 同样,n &gt;=10,所以对smallD(12 / 10)(即smallD(1))的调用发生了。为这个新调用创建了一个堆栈帧(称为C),带有n = 1

    • 现在n &lt; 10 持有!最后一个函数调用(在堆栈帧C)返回值1并删除它自己的堆栈帧(帧C)。

    • 堆栈帧B(其中n = 12)现在位于顶部,smallD(12) 的执行仍在继续。由于堆栈帧C 已返回1,堆栈帧B 现在包含x = 1。比较(x &lt; n % 10)为真(1 &lt; 2),堆栈帧B返回x = 1

    • 同样的情况再次发生在堆栈帧A 中,堆栈帧A(对应于我们最初的smallD(123) 调用)返回1

    所以你看,确实发生了“除法直到 n smallD。

    【讨论】:

      【解决方案4】:

      您几乎可以将每个递归调用分解为基本情况(最简单的)和递归情况。在这种情况下:

      • 基本情况是数字只有一位数:这意味着小于 10(这是第一个两位数的数字)。
        if (n < 10) return n;
        
        没有其他数字可以比较,所以直接返回。
      • 递归案例是任何其他不是基础的案例。现在我们的数字有两个或多个数字,所以:
        • 并通过递归调用x = smallD(n / 10) 了解除最后一位以外的所有数字中最小的数字是什么
        • 然后我们将结果与我们之前未包含的最后一位数字进行比较,并返回最小的。

      为了更好地了解调用背后的过程,您可以打印一些信息并观察它。试试这样的:

      int smallD(int n) {
          if (n < 10){
            std::cout << "Base case. Return " << n << std::endl;
            return n;
          }
          std::cout << "Recursive case: " << n << std::endl;
          std::cout << "Compare " << n % 10 << " with smallest of " << n/10 << std::endl;
          int x = smallD(n / 10);
          int ret;
          if (x < n % 10) ret = x;
          else ret = n % 10;
          std::cout << "Smallest of " << n << " is " << ret << std::endl;
          return ret;
      }
      

      【讨论】:

        猜你喜欢
        • 2016-11-01
        • 2011-03-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-04-03
        • 1970-01-01
        • 2021-11-14
        • 2020-08-18
        相关资源
        最近更新 更多