Ever had a difficulty converting recursive algorithms to non-recursive alternatives? I did, In this blog I will be showing a generalized way of converting (most of the) recursive algorithms to its non-recursive alternatives.
I discovered this technique not so long ago when I stumbled upon one of Tsoding's (Tsoding channel) videos. In this video, Zozen shows how to implement: Inverting a Binary Tree in Rust.
I will be showing solutions in Python (for my convenience) to demonstrate this method.
In most programming languages whenever a function is called, all the data required by the function is packed together and "pushed" onto something called - The CALL Stack (more on this here.). Recursive functions rely on this CALL stack to ensure mutual exclusivity of data between separate calls.
The function returns values to the caller by using another stack, called the RETURN stack. Whenever the function execution is complete, it "pushes" the returned value onto the RETURN Stack. Then the caller just has to inspect the RETURN stack for the value.
As most programming languages have a limit on the size of these stacks, recursive solutions often face "Stack Overflow" errors when the recursive calls are too deep.
We can fix this by simply emulating this process by allocating our own stacks onto the HEAP storage.
It will be easier to show this by examples-
First I will implement tower of hanoi as recursive algorithm-
move_disc
is just a helper function to format instruction for moving disk
from top of
frm
tower to to
tower as string.
If you see the recursive implementation, you can observe the following points-
We can generalize these operations as CALL
for recursive calls,
and HANDLE
for doing things with the input.
Following is the general structure of non-recursive alternative-
CALL as 0
and HANDLE as 1
.
(they can be any two things, it just has to be different, we could have picked True and False for instance).call_stack
with initial input tagged with CALL
value.call_stack
and depending on the action we handle things differently. At the first pop
the value of action will definitelty be a CALL
CALL
section, we append furthur actions - CALL
and/ or HANDLE
depending on the algorithm.
CALL
for the recursive call, and HANDLE
if have to do something
with the data (Note that these actions are not yet evaluated yet, we use
the call_stack
to delay the actions).HANDLE
section we do operations with the dataNon-recursive solution for tower of hanoi-
The above algorithm does not depend upon the Programming Language's call stack, hence theoretically is unbounded, and free from Stack Overflow errors.
So far we just printed the data to the console, what if we want to emulate returning values from functions.
For that we use return_stack
So, the generalized structure changes a little-
return_stack
as empty []
return_stack
in the CALL
section, usually for the base case.return_stack
in HANDLE
section, and use that data to compute
something, and push that something to return_stack
for other HANDLE
section to use.return_stack
Let's use this to solve the fibonacci problem
Recursive Fibonacci Algorithm-
Non-recursive Fibonacci Algorithm-
That's pretty much it. Let's solve other problems using this technique to solidify our understanding-
Types to store binary tree data-
Recursive Algorithm-
Non-recursive alternative-
Types for Binary Tree data-
Recursive Algorithm-
Non-recusrive alternative-
There are many examples (including above) here.
True, the non-recursive alternative is longer and a little difficult to understand, but it's very easy to produce once you have a recursive solution, and CALL and HANDLE gives us a way to segregate and identify parts of our algorithm.
And with that, I will wrap this up. Thank you if you are still reading. Happy Coding & Keep Learning 😃