Basically, tail recursion is what you have when a function calls itself, but doesn't need to do anything after that, other than possibly return the result of the recursive call. It's a special case of recursion...not in how it works, but in the fact that it is easy to turn into a loop instead. For compilers/interpreters that don't optimize tail recursion, that can mean the difference between working correctly and overflowing the call stack.
For example:
void countdown(int t) {
if (t <= 1) return;
printf("%d\n", t);
countdown(t - 1);
}
This is tail-recursive, since nothing else needs to happen after countdown
calls itself.
Contrast with something like
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
This is not tail-recursive. The difference is that the function needs to regain control after it calls itself, so it can multiply the result by n
.
(Bonus extra point though: you can turn this into a tail-recursive function using a technique called "continuation-passing style". Basically, in this case, you'd pass the result along with the other parameters.)
int factorial(int n, int product = 1) {
if (n <= 1) return product;
return factorial(n - 1, product * n);
}
But i digress.
Anyway, compilers that remove tail recursion basically just tweak the current call frame so that the variables are what they'd have been for a call, and then jump back to the beginning of the function. Your teacher probably isn't going to like that very much, for a few good reasons. But let's start there.
selection_sort(int a, int b){
tail_call:
if(a == b+1){
//do nothing
}else{
i = minIndex(arr, a, b);
if(i != a)
swap(arr, i, a);
// tweak the variables so the next iteration sees (a+1, b)
a += 1;
goto tail_call;
}
}
While we're rearranging stuff, empty conditional blocks are rather hideous. This can also be rewritten as
selection_sort(int a, int b){
tail_call:
if(a != b+1){
i = minIndex(arr, a, b);
if(i != a)
swap(arr, i, a);
// tweak the variables so the next iteration sees (a+1, b)
a += 1;
goto tail_call;
}
}
Now, you've probably been told that goto
is the devil. :) (That's overstated, but it shouldn't be used where there's a better option.) So refactor to get rid of it.
How do we get rid of it? We find a control structure that does what the goto
was trying to do. And in this case, a while
loop fits the bill quite nicely. Absent scope differences in condition
, this code:
loop:
if (condition) {
...magic...
goto loop;
}
does exactly the same thing as this:
while (condition) {
...magic...
}
to the point where many compilers will even generate the exact same code for both.
So let's clean up the code with a while
loop.
selection_sort(int a, int b){
while (a != b+1) {
int i = minIndex(arr, a, b);
if(i != a)
swap(arr, i, a);
a += 1;
}
}