Related
This question already has answers here:
Understanding recursion [closed]
(20 answers)
Closed 5 years ago.
Before you get started, I have used google countless times in hopes of searching for a very brief and simple explanation of how recursion works when it has a return type. But I guess I'm not as bright as I thought since i still cant understand it quite well.
Take the following code snippet (in java) as an example
public static int recursion(int num)
{
int result;
if (num == 1)
result = 1;
else
result = recursion(num - 1) + num;
return result;
}
I grabbed this code from my professors lecture slide and he said this will return 1 + 2 + 3 + ... + num.
I just need someone to explain how the process works in the method that i provided. Maybe a step by step approach might help me understand how recursion works.
recursion(5) = recursion(4) + 5, let's figure out recursion(4) and come back to this later
recursion(4) = recursion(3) + 4, let's figure out recursion(3) and come back to this later
recursion(3) = recursion(2) + 3, ...
recursion(2) = recursion(1) + 2, ...
recursion(1) = 1, we know this!
recursion(2) = 1 + 2, now we can evaluate this
recursion(3) = (1+2) + 3, and now we can evaluate this
recursion(4) = (1+2+3) + 4, ...
recursion(5) = (1+2+3+4) + 5, the answer to our original question
Note: Without knowing recursion(1), we'd have gone to 0, -1, -2, and so on until forever. This known quantity is called the base case and it is a requirement for recursion.
Basically when there is a stack buildup for each item that is created beyond the last iteration. (Where num=1)
When n>1 the if statement kicks the iteration to the else which 'saves' the result in a stack and calls the same funtion again with n-1
what this effectively does is keep calling the same function until you hit your designated 'base case' which is n=1
Recursion is all about solving a problem by breaking it into a smaller problem. In your case, the question is "how do you sum the numbers from 1 to n", and the answer is "sum up all the numbers from 1 to n-1, and then add n to it". You've phrased the problem in terms of a smaller or simpler version of itself. This often involves separating out a "base case"—an irreducibly simple problem with a straightforward answer.
public static int recursion(int num)
{
int result;
if (num == 1)
result = 1; // Base case: the sum of the numbers from 1 to 1 is 1.
else
result =
// This is the sum of numers from 1 to n-1. The function calls itself.
recursion(num - 1)
// Now add the final number in the list, and return your result.
+ num;
return result;
}
You're defining the unsolved problem in terms of itself, which works because the solution always involves either the base case or a simpler version of the problem (which itself further involves either the base case or an even simpler version of the problem).
I'll close with one of my favorite jokes:
How do you explain recursion to a five-year-old?
You explain recursion to a four-year-old, and wait a year.
Going by the classic code example you posted. if you call your method like so with number passed in as 5:
recursion(5);
In layman terms just to understand, your function will create & call another copy of your function in the else block as below:
recursion(4);
and then
recursion(3);
recursion(2);
recursion(1);
as the number keeps decrementing.
Finally it will call the if part in the final copy of the method as num will satisfy num == 1. So from there it starts unwinding & returning each value to the previous call.
As each method call has its own stack to load method local variables on, there will be n number of stacks created for n calls. When the deepest call in recursion is made, then the stacks start unwinding. Hence recursion achieved
The most important thing however to note is that there is a base-most call in your code, which is done at 1 just because you have the check if (num == 1). Else it would be infinite recursion & of course a fatal & wrong program to write. The base-most call is from where its called as stack unwinding in recursion terms.
Example: Finding the factorial of a number is the most classic examples of recursion.
Performance: Do look into recursion vs iteration and recursion vs looping to see what are the performance impacts of recursion
This is problem 9.5 from Cracking the Coding Interview 5th edition
The Problem: Write a method to compute all permutations of a string
Here is my solution, coded in Java(test it, it works :) )
public static void generatePerm(String s) {
Queue<Character> poss = new LinkedList<Character>();
int len = s.length();
for(int count=0;count<len;count++)
poss.add(s.charAt(count));
generateRecurse(poss, len, "");
}
private static void generateRecurse(Queue<Character> possibles, int n, String word) {
if(n==0)
System.out.println(word);
else {
for(int count=0;count<n;count++) {
char first = possibles.remove();
generateRecurse(possibles, n-1, word+first);
possibles.add(first);
}
}
}
I agreed with the author that my solution runs in O(n!) time complexity because to solve this problem, you have to consider factorials, like for a word like "top", there are three possibilities for the first letter, 2 for the second and so on....
However she didn't make any mention of space complexity. I know that interviewers love to ask you the time and space complexity of your solution. What would the space complexity of this solution be? My initial guess was O(n2) because there are n recursive calls at each level n. So you would add n + n - 1 + n - 2 + n - 3.....+ 1 to get n(n+1)⁄2 which is in O(n2). I reasoned that there are n recursive calls, because you have to backtrack n times at each level and that space complexity is the number of recursive calls your algorithm makes. For example, when considering all the permutations of "TOP", at level, 3 recursive calls, gR([O,P],2,"T"), gR([P,T],2,"O"), gR([T,O],2,"P") are made. Is my analysis of space complexity correct?
I think you got the right answer but for the wrong reason. The number of recursive calls doesn't have anything to do with it. When you make a recursive call, it will add a certain amount of space to the stack; but when that call exits, the stack space is released. So suppose you have something like this:
void method(int n) {
if (n == 1) {
for (int i = 0; i < 10000; i++) {
method(0);
}
}
}
method(1);
Although method calls itself 10000 times, there will still be no more than 2 invocations of method on the stack at any one time. So the space complexity would be O(1) [constant].
The reason your algorithm has space complexity O(n2) is because of the word string. When n gets down to 0, there will be len stack entries being taken up by invocations of generateRecurse. There will be len stack entries at most, so the space usage of the stack will only be O(n); but each of those stack entries has its own word, which will all exist on the heap at the same time; and the lengths of those word parameters are 1, 2, ..., len, which of course do add up to (len * (len+1)) / 2, which means the space usage will be O(n2).
MORE ABOUT STACK FRAMES: It appears that an explanation of the basics of stack frames would be helpful...
A "stack frame" is just an area of memory that's part of the "stack". Typically, the stack is a predefined area of memory; the location and size of stack frames, however, are not predefined. When a program is first executed, there won't be anything on the stack (actually, there will probably be some initial data there, but let's say there's nothing, just to keep things simple). So the stack area of memory looks like this:
bottom of stack top of stack
------------------------------------------------------------------
| nothing |
------------------------------------------------------------------
^
+--- stack pointer
(This assumes that the stack grows upward, from lower to higher addresses. Many machines have stacks that grow downward. To simplify, I'll keep assuming that this is a machine whose stack grows upward.)
When a method (function, procedure, subroutine, etc.) is called, a certain area of the stack is allocated. The area is enough to hold the method's local variables (or references to them), parameters (or references to them), some data so that the program will know where to go back when you return, and possibly other information--the other information is highly dependent on the machine, the programming language, and the compiler. In Java, the first method will be main
bottom of stack top of stack
------------------------------------------------------------------
| main's frame | nothing |
------------------------------------------------------------------
^
+--- stack pointer
Note that the stack pointer has moved up. Now main calls method1. Since method1 will return to main, the local variables and parameters of main have to be preserved for when main gets to resume executing. A new frame, of some size, is allocated on the stack:
bottom of stack top of stack
------------------------------------------------------------------
| main's frame | method1's frame | nothing |
------------------------------------------------------------------
^
+--- stack pointer
and then method1 calls method2:
bottom of stack top of stack
------------------------------------------------------------------
| main's frame | method1's frame | method2's frame | nothing |
------------------------------------------------------------------
^
+--- stack pointer
Now method2 returns. After method2 returns, its parameters and local variables will no longer be accessible. Therefore, the entire frame can be thrown out. This is done by moving the stack pointer back to where it was before. (The "previous stack pointer" is one of the things saved in some frame.) The stack goes back to looking like this:
bottom of stack top of stack
------------------------------------------------------------------
| main's frame | method1's frame | nothing |
------------------------------------------------------------------
^
+--- stack pointer
This means that, at this point, the machine will see the portion of the stack starting with the stack pointer as "unused". It's not really correct to speak of method2's frame being reused. You can't really use something that has ceased to exist, and method2's frame no longer exists. Conceptually, all there is is a big empty area of the stack. If method1 calls another method, whether it's method2, method1 recursively, System.out.println, or something else, a new frame will be created at the place where the stack pointer is now pointing. This frame could be smaller, equal, or larger in size than the method2 frame used to be. It will take up part or all of the memory where the method2 frame was. If it's another call to method2, it doesn't matter whether it's called with the same or different parameters. It can't matter, because the program doesn't remember what parameters were used last time. All it knows is that the area of memory starting with the stack pointer is empty and available for use. The program has no idea what frame most recently lived there. That frame is gone, gone, gone.
If you can follow this, you can see that when computing the space complexity and when looking just at the amount of space used by the stack, the only thing that matters is, how many frames can exist on the stack at any one point in time? Frames that may have existed in the past but no longer do are not relevant to the computation, no matter what parameters the methods were called with.
(P.S. In case anyone was planning to point out how I'm technically wrong about this or that detail--I already know that this is a gross oversimplification.)
My problem is that I usually get a java.lang.StackOverflowError when I use recursion.
My question is - why does recursion cause stackoverflow so much more than loops do, and is there any good way of using recursion to avoid stack overflow?
This is an attempt to solve problem 107, it works well for their example but runs out of stack space for the problem it self.
//-1 16 12 21 -1 -1 -1 16 -1 -1 17 20 -1 -1 12 -1 -1 28 -1 31 -1 21 17 28 -1 18 19 23 -1 20 -1 18 -1 -1 11 -1 -1 31 19 -1 -1 27 -1 -1 -1 23 11 27 -1
public class tries
{
public static int n=7,min=Integer.MAX_VALUE;
public static boolean[][] wasHere=new boolean[n][60000];
public static void main(String[] args)
{
int[] lines=new int[n]; Arrays.fill(lines, -1000); lines[0]=0;
int[][] networkMatrix=new int[n][n];
Scanner reader=new Scanner(System.in);
int sum=0;
for(int k=0; k<n; k++)
{
for(int r=0; r<n; r++)
{
networkMatrix[k][r]=reader.nextInt();
if(networkMatrix[k][r]!=-1) sum+=networkMatrix[k][r];
Arrays.fill(wasHere[k], false);
}
}
recursive(lines,networkMatrix,0,0);
System.out.println((sum/2)-min);
}
public static void recursive(int[] lines, int[][] networkMatrix, int row,int lastRow)
{
wasHere[row][value((int)use.sumArr(lines))]=true;
if(min<sum(lines)) return;
if(isAllNotMinus1000(lines)) min=sum(lines);
int[][] copyOfMatrix=new int[n][n];
int[] copyOfLines;
for(int i=0; i<n; i++)
{
copyOfLines=Arrays.copyOf(lines, lines.length);
for(int k=0; k<n; k++) copyOfMatrix[k]=Arrays.copyOf(networkMatrix[k], networkMatrix[k].length);
if(i!=0&©OfMatrix[i][row]!=0) copyOfLines[i]=copyOfMatrix[i][row];
copyOfMatrix[i][row]=0; copyOfMatrix[row][i]=0;
if(networkMatrix[row][i]==-1) continue;
if(wasHere[i][value((int)use.sumArr(copyOfLines))]) continue;
if(min<sum(copyOfLines)) continue;
recursive(copyOfLines,copyOfMatrix,i,row);
}
}
public static boolean isAllNotMinus1000(int[] lines)
{
for(int i=0; i<lines.length; i++) {if(lines[i]==-1000) return false;}
return true;
}
public static int value(int n)
{
if(n<0) return (60000+n);
return n;
}
public static int sum(int[] arr)
{
int sum=0;
for(int i=0; i<arr.length; i++)
{
if(arr[i]==-1000) continue;
sum+=arr[i];
}
return sum;
}
}
why does recursion cause stackoverflow so much more than loops do
Because each recursive call uses some space on the stack. If your recursion is too deep, then it will result in StackOverflow, depending upon the maximum allowed depth in the stack.
When using recursion, you should be very careful and make sure that you provide a base case. A base case in recursion is the condition based on which the recursion ends, and the stack starts to unwind. This is the major reason of recursion causing StackOverflow error. If it doesn't find any base case, it will go into an infinite recursion, which will certainly result in error, as Stack is finite only.
In most cases, a stack overflow occurs because a recursive method was ill-defined, with a non-existent or unreachable ending condition, which causes the stack memory space to be exhausted. A correctly written recursion should not produce a stack overflow.
However, there are situations where a method can produce a stack overflow even if it was correctly implemented. For instance:
A fast-growing (say, exponential) recursion. For example: the naive recursive implementation of the Fibonacci function
A very big input data, that will eventually cause the stack space to be exhausted
Bottom line: it all depends on the particular case, it's impossible to generalize regarding what causes a stack overflow.
Each recursive call uses some space on the stack (to house anything specific to that one call, such as arguments, local variables, etc.). Thus, if you make too many recursive calls (either by not correctly providing a base case or just by trying to do too many recursive calls), then there is not enough room to provide space for it all, and you end up with a StackOverflow.
The reason why loops do not have this problem is that each iteration of a loop does not use its own unique space (i.e. if I loop n times, I don't need extra space to do the n+1st loop).
The reason why the recursion causes stack overflow is because we fail to establish when the recursion should stop, and thus the function/method will keep calling itself "forever" (until it causes the error). You will have the same problem even if you are using loops, if you have something as the following:
bool flag = true;
while (flag == true){
count++;
}
Since flag will always be true, the while loop will never stop until it gives you the stack overflow error.
Every level of recursion that you go down, you are add state information to the runtime stack. This information is stored in an activation record and contains information like which variables are in scope and what their values are. Loops do not have extra activation records each time you loop so they take less memory.
In certain situations your recursion may go deep enough that it causes the stack to overflow but there are ways to help prevent this from happening. When working with recursion, I usually follow this format:
public obj MyMethod(string params) {
if (base-case) {
do something...
} else {
do something else...
obj result = MyMethod(parameters here);
do something else if needed..
}
}
Recursion can be super effective and do things that loops cannot. Sometimes you just get to a point where recursion is the obvious decision. What makes you a good programmer is being able to use it when it is not completely obvoius.
When properly used, recursion will not produce a StackOverflowError. If it does, then your base case is not being triggered, and the method keeps calling itself ad infinitum. Every method call that does not complete remains on the stack, and eventually it overflows.
But loops don't involve method calls by themselves, so nothing builds up on the stack and a StackOverflowError does not result.
Every time you call a method, you consume a "frame" from the stack, this frame is not released until the method returns, it doesn't happen the same with loops.
recursion causes stack overflow cause all the previous calls are in memory. so your method calls itself with new parameters, then that again calls itself. so all these calls stack up and normally can run out of memory.
loops store the results normally in some variables and call the methods which is like a new fresh call to methods, after each call, the caller methods ends and returns results.
As in my opinion, getting error as StackOverFlow in Recursion due to :
not implemented the recursion correctly which results in infinite recursion, so check out the base case, etc.
If your input is large, it preferred to use Tail Recursion to avoid StackOverflow.
Here for loop is used inside the recursive function. When the recursive function is called, for(int i=0; i<n; i++) the value of i is initialized to zero, as it calls itself, the value of i will again be initialized to zero and it conitues infintely. This will lead you to Stack overflow error.
Solution: Avoid for loop inside recursive function; instead go for while or do-while and initialize the value of i outside recursive function
This is the context of my program.
A function has 50% chance to do nothing, 50% to call itself twice.
What is the probability that the program will finish?
I wrote this piece of code, and it works great apparently. The answer which may not be obvious to everyone is that this program has 100% chance to finish. But there is a StackOverflowError (how convenient ;) ) when I run this program, occuring in Math.Random(). Could someone point to me where does it come from, and tell me if maybe my code is wrong?
static int bestDepth =0;
static int numberOfPrograms =0;
#Test
public void testProba(){
for(int i = 0; i <1000; i++){
long time = System.currentTimeMillis();
bestDepth = 0;
numberOfPrograms = 0;
loop(0);
LOGGER.info("Best depth:"+ bestDepth +" in "+(System.currentTimeMillis()-time)+"ms");
}
}
public boolean loop(int depth){
numberOfPrograms++;
if(depth> bestDepth){
bestDepth = depth;
}
if(proba()){
return true;
}
else{
return loop(depth + 1) && loop(depth + 1);
}
}
public boolean proba(){
return Math.random()>0.5;
}
.
java.lang.StackOverflowError
at java.util.Random.nextDouble(Random.java:394)
at java.lang.Math.random(Math.java:695)
.
I suspect the stack and the amount of function in it is limited, but I don't really see the problem here.
Any advice or clue are obviously welcome.
Fabien
EDIT: Thanks for your answers, I ran it with java -Xss4m and it worked great.
Whenever a function is called or a non-static variable is created, the stack is used to place and reserve space for it.
Now, it seems that you are recursively calling the loop function. This places the arguments in the stack, along with the code segment and the return address. This means that a lot of information is being placed on the stack.
However, the stack is limited. The CPU has built-in mechanics that protect against issues where data is pushed into the stack, and eventually override the code itself (as the stack grows down). This is called a General Protection Fault. When that general protection fault happens, the OS notifies the currently running task. Thus, originating the Stackoverflow.
This seems to be happening in Math.random().
In order to handle your problem, I suggest you to increase the stack size using the -Xss option of Java.
As you said, the loop function recursively calls itself. Now, tail recursive calls can be rewritten to loops by the compiler, and not occupy any stack space (this is called the tail call optimization, TCO). Unfortunately, java compiler does not do that. And also your loop is not tail-recursive. Your options here are:
Increase the stack size, as suggested by the other answers. Note that this will just defer the problem further in time: no matter how large your stack is, its size is still finite. You just need a longer chain of recursive calls to break out of the space limit.
Rewrite the function in terms of loops
Use a language, which has a compiler that performs TCO
You will still need to rewrite the function to be tail-recursive
Or rewrite it with trampolines (only minor changes are needed). A good paper, explaining trampolines and generalizing them further is called "Stackless Scala with Free Monads".
To illustrate the point in 3.2, here's how the rewritten function would look like:
def loop(depth: Int): Trampoline[Boolean] = {
numberOfPrograms = numberOfPrograms + 1
if(depth > bestDepth) {
bestDepth = depth
}
if(proba()) done(true)
else for {
r1 <- loop(depth + 1)
r2 <- loop(depth + 1)
} yield r1 && r2
}
And initial call would be loop(0).run.
Increasing the stack-size is a nice temporary fix. However, as proved by this post, though the loop() function is guaranteed to return eventually, the average stack-depth required by loop() is infinite. Thus, no matter how much you increase the stack by, your program will eventually run out of memory and crash.
There is nothing we can do to prevent this for certain; we always need to encode the stack in memory somehow, and we'll never have infinite memory. However, there is a way to reduce the amount of memory you're using by about 2 orders of magnitude. This should give your program a significantly higher chance of returning, rather than crashing.
We can do this by noticing that, at each layer in the stack, there's really only one piece of information we need to run your program: the piece that tells us if we need to call loop() again or not after returning. Thus, we can emulate the recursion using a stack of bits. Each emulated stack-frame will require only one bit of memory (right now it requires 64-96 times that, depending on whether you're running in 32- or 64-bit).
The code would look something like this (though I don't have a Java compiler right now so I can't test it):
static int bestDepth = 0;
static int numLoopCalls = 0;
public void emulateLoop() {
//Our fake stack. We'll push a 1 when this point on the stack needs a second call to loop() made yet, a 0 if it doesn't
BitSet fakeStack = new BitSet();
long currentDepth = 0;
numLoopCalls = 0;
while(currentDepth >= 0)
{
numLoopCalls++;
if(proba()) {
//"return" from the current function, going up the callstack until we hit a point that we need to "call loop()"" a second time
fakeStack.clear(currentDepth);
while(!fakeStack.get(currentDepth))
{
currentDepth--;
if(currentDepth < 0)
{
return;
}
}
//At this point, we've hit a point where loop() needs to be called a second time.
//Mark it as called, and call it
fakeStack.clear(currentDepth);
currentDepth++;
}
else {
//Need to call loop() twice, so we push a 1 and continue the while-loop
fakeStack.set(currentDepth);
currentDepth++;
if(currentDepth > bestDepth)
{
bestDepth = currentDepth;
}
}
}
}
This will probably be slightly slower, but it will use about 1/100th the memory. Note that the BitSet is stored on the heap, so there is no longer any need to increase the stack-size to run this. If anything, you'll want to increase the heap-size.
The downside of recursion is that it starts filling up your stack which will eventually cause a stack overflow if your recursion is too deep. If you want to ensure that the test ends you can increase your stack size using the answers given in the follow Stackoverflow thread:
How to increase to Java stack size?
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. i.e. 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).