简体   繁体   中英

How does this recursive function work in C++?

I'm currently taking a class for c++ and we are learning about recursion and in class my professor used this function as an example of recursion, the function is meant to return the smallest digit in a number and is:

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

I'm confused on how setting x to the recursive call works, would the function not keep running n / 10 until n is < 10, I just really don't understand the concept and could use some pointers as to how this function works.

Here's something that helps to understand recursion. Add print statements to observe the code as it recursively calls itself and pass and "indent level" to help as well.

Take the original minified code and expand it to something more readable and add extra debugging information to it.

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;
}

Let's try it out by invoking 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

So hopefully, that will help you understand how the recursion works.

Recursive functions work exactly like non-recursive functions.
One common mistake is to try to think about all the recursive calls at once, as if they had a shared state, but one key ingredient to understanding a recursive function is actually to ignore the recursion and just "think locally".

Perhaps working through an example would clarify things.
Let's look at smallD(321) , replacing n in the body of the function with its value.

smallD(321)

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

The first condition is clearly false, and in order to determine x , we need smallD(321/10) , which is smallD(32) .

smallD(32)

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

The first condition is false again, so we keep going with 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;

Now the first condition is true, so the result here is clearly 3 .
Now we can go back and use the value of x in each call that was left waiting.

smallD(32)

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

And 3 < 32 % 10 is false, so we return 32 % 10 - 2 to the caller.

smallD(321)

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

And 2 < 321 % 10 is false, so we return 321 % 10 , which is 1 .

Your intuition is not completely off: The function does indeed "keep running n/10 until n is <10" - but it's in different calls to the same function.

Your program keeps a stack of functions calls. Each function you call puts a (so-called) "frame" on top of everything that is currently on the stack. Inside that frame "live" all the variables "belonging" to that function call. When the function exits, it deletes its own frame from the stack. The currently executing function's frame is on the top of the stack. So, let's see what happens if you call smallD(123) :

  • You start with something else on the stack, at least your main() . Your call to smallD(123) puts the frame for smallD(123) on the top of the stack. This frame contains the variable n = 123 . Lets call this frame stack frame A .

  • Since n >= 10 , your smallD(123) calls smallD(123 / 10) , ie, smallD(12) (integer division basically truncates in C++). So, you put another frame on top of your stack. This stack frame corresponds to smallD(12) and contains the variable n = 12 . Let's call this stack frame B .

  • Again, n >=10 , so the call to smallD(12 / 10) (ie, smallD(1) ) happens. A stack frame (call it C ) is created for this new call, with n = 1 .

  • Now n < 10 holds! The last function call (in stack frame C ) returns the value 1 and deletes its own stack frame (frame C ).

  • Stack frame B (where n = 12 ) is now on top and execution of smallD(12) continues. Since stack frame C has returned 1 , stack frame B now contains x = 1 . The comparison (x < n % 10) is true ( 1 < 2 ), and stack frame B return x = 1 .

  • The same happens in stack frame A again, and stack frame A (which corresponds to our original smallD(123) call) returns 1 .

So you see, indeed "division until n < 10" happened, but in different (recursive!) calls to smallD .

You can break down almost every recursive call into the base case (the simplest) and the recursive case . In this case:

  • The base case is when the number has only one digit : this means that is lesser than 10 (which is the first number with two digits).
     if (n < 10) return n;
    There is no other digit to compare it to, so return it directly.
  • The recursive case is any other case that is not the base. Now our digit has two or more digits, so:
    • And learn what is the smallest digit of all the digits except the last one with the recursive call x = smallD(n / 10)
    • Then we compare the result to the last digit, which we didn't include before, and return the smallest.

To understand better the process behind the calls, you could print some information and observe it. Try something like this:

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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM