简体   繁体   中英

Understanding recursion in Java a little better

Ok I'm really confused about something about recursion in Java. Say I have the following code:

static int findShortestString(String[] paths, int lo, int hi) {
        if(lo==hi)
            return lo;
        int minindex=findShortestString(paths,lo+1, hi);
        if(safeStringLength(paths[lo])<safeStringLength(paths[minindex]))
            return lo;
        return minindex;

Now the question is not really about the code itself, but just about how recursion works. minindex is being set equal to a recursive call. So the first time the function run and tries to set minindex to something, it does so, and then the function calls itself. But when does the if statement run then? Will it only run when minindex finally actually holds a real value? I just cant wrap my head around this. If minindex causes the function to recurse and recurse, then when will the if statement ever be checked? When lo==hi ? I dont get it:(

minindex is not assigned until findShortestString returns , which won't happen until lo == hi .

Each time the method calls itself, it narrows the difference between lo and hi by 1, so eventually they'll be equal* and that value will be returned.

An example, with paths = ["p1", "path2", "longpath3"] :

lo = 0, hi = 2
lo != hi -> call findShortestString(paths, 1, 2)

  lo = 1, hi = 2
  lo != hi -> call findShortestString(paths, 2, 2)

    lo = 2, hi = 2
    lo == hi -> return lo (=2)

  lo = 1, hi = 2, minindex = 2
  length of "path2" < length of "longpath3" -> return lo (= 1)

lo = 0, hi = 2, minindex = 1
length of "p1" < length of "path2" -> return lo (= 0)

I've tried to illustrate the variable values at each level of recursion using increasing amounts of indentation. At the beginning of each recursive call, the previous values of lo , hi and minindex are saved off (in a structure called a "stack") and the new values used instead. When each invocation of the method returns , the previously saved values are "popped" off the stack for use, and minindex assigned from the previous return value.

*unless lo > hi to begin with, I guess...

Here's a play by play of the execution:

You call findShortestString() yourself

if lo doesn't not equal hi things continue. Otherwise they stop here and the function returns.

Once you call findShortestString() again, everything in this instance of the function completely stops and will not resume until the computer has a value to give minindex (aka the function returns.) We start over in a new instance of the function at the top. The only code executed until one of the functions return is the code BEFORE the method call. This could be compared to a while loop.

We only get beyond that line once one of the function instances has lo==hi and returns.

Control switches to the function instance before that, which assigns the returned lo value to minindex .

If (safeStringLength(paths[lo])<safeStringLength(paths[minindex])) then we return lo . Else, we return minindex . Either way, this function instance is complete and control returns to the one before it.

Each function called is now only executing the code AFTER the method call, as the method will not get called again. We are unwinding the stack of calls. All of the returns will now be from the last 2 statements, as the code at the top does not get executed again. Note how only one function instance returns with the top part of the code, terminating the while loop. All the rest terminate with the return statements in the part of the function after the recursive call.

Eventually the last function returns and you go back to the code you called the function from originally.

Here's a more readable version of what the code is actually doing:

In the code before the recursive call, all that happens is the creation of a chain of calls until lo==hi . Each time the function is called with lo being 1 greater. Here's a sample stack of calls:

findShortestString(2,5);
findShortestString(3,5);
findShortestString(4,5);
findShortestString(5,5);

When they unwind, each function instance compares the string lengths of the strings at the indexes lo and the index the previous index with the shortest string.

compare strings at indexes 2 and 5
if the string at 2 is smaller, compare the strings at indexes 2 and 4.
Otherwise, compare the strings with indexes at 3 and 5.

If lo>hi at the beginning, the code will continue to run until lo overflows an integer and becomes negative, then until lo finally gets all the way up to hi, or 4,94,967,296 - (original lo - original hi). In other words, in will take a long time . To fix this, add a check at the beginning of the method that throws an exception if lo>hi .

The code could be better rewritten as this:

static int findShortestString(String[] paths, int lo, int hi) {
    int indexWithShortestString=lo;
    for( int i=lo; i<=hi-1; i++) {
        //assumption: lo and hi are both valid indexes of paths
        if (paths[i+1].length < paths[i].length)
            indexWithShortestString=i+1;
     }
}

Think of a stack. Every time the recursive method is called a new "frame" is put on top of the stack. The frame contains its own "slots" for each variable, independent and distinct from those in the frames below.

Eventually, a new frame will be created where the value of lo and hi are equal, and the method will return without pushing another frame. (This is called the "base case".) When that return occurs, that frame is popped off the stack, and the frame that was just below it continues its execution at the second if statement. Eventually that frame is also popped off and the same happens to the frame just below, and so on, until execution returns to the original caller.

Each time findShortestString calls itself, minindex will eventually be assigned, then the value is used in the if statement. It is always set the index of the shortest string at a higher index than lo .

So if there is a call stack with 10 levels of findShortestString, minindex is assigned 9 times (the first call is from another function).

This is a really confusing recursive function. But you generally have it correct. Every call to findShortestString() will push the function onto the stack. It will keep doing this until lo==hi. At that point, the stack is unwound and corresponding recursive calls will be assigned to their corresponding ints.

In this function, it seems that you'll only ever be returning lo . Because either (safeStringLength(paths[lo])<safeStringLength(paths[minindex]) will be true and you'll return lo . Or lo==hi will be true and you'll return lo

In order for the statement

int minindex=findShortestString(paths,lo+1, hi);

to evaluate, the method call findShortestString(paths,lo+1, hi) must return a value. Thus the following if statement will not happen until this method call returns a value. However, this method might call itself again, and you get a nesting effect.

Basically an execution of a function ends when a return statement is called. Everything after a return statement which is called no longer matters (or "exists").

Hence, the local variable minindex will only exist in an execution of a findShortestString function when the first if-statement is false.

Treat each execution of a findShortestString function independently, whether they are called recursively or from somewhere else in the code. ie different execution of a findShortestString function may return at different paths and have their own values and local variables. Depending on the input values, they may return at line 3, 6 or 7.

minindenx only exists in an execution that can run line 4, and it is assigned findShortestString(paths,lo+1, hi) which is guaranteed have a value, if the code is correct, otherwise you will get an infinite recursion, resulting in a stack overflow (pun unintended).

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