~/Home ~/Notes ~/Categories

Recursion

23 August, 2020  ·   Dsa Recursion

Intro to recursion

What is recursion?

Recursion : A function calling itself directly or indirectly.

Directly:

1void fun1(){
2    //somecode ...
3    fun1();
4    //some other code...
5}

Indirectly: Not a very common approach (Mostly direct recursive approach is used)

fun1() -> fun2() -> fun1()

 1void fun1(){
 2    //somecode ...
 3    fun2();
 4    //some other code...
 5}
 6void fun2(){
 7    //somecode ...
 8    fun1();
 9    //some other code...
10}
11
12

Example:

 1void fun1(int n)
 2{
 3    //Base Case
 4    if (n == 0)
 5        return;
 6    cout << "Function 1" << endl;
 7    fun1(n - 1);
 8}
 9int main(int argc, char const *argv[])
10{
11    /* code */
12    fun1(2);
13    return 0;
14}
15

Typical structure of a recursion

(Assuming: You are not using global vars or some other tricks to stop recursion ).

1returntype function(parameters){
2    Base cases
3    //some other code.....
4    Recursive call (i.e., call to function()) with atleast one change in parameters
5    //some other code...
6}

Applications of recursion

Any problem which can be solved iteratively can also be solved using recursively and vice-versa. They both have same expressive power.

Now the question is if we can write equivalent iterative code for every recursive code or vice versa, where do we use recursion?

Examples

 1void fun1(int n)
 2{
 3
 4    if (n == 0)
 5        return; //Base Case
 6    cout << n << endl;
 7    fun1(n - 1);
 8    cout << n << endl;
 9}
10int main(int argc, char const *argv[])
11{
12    /* code */
13    fun1(3);
14    return 0;
15}
16//Output : 321123
 1void fun1(int n)
 2{
 3
 4    if (n == 0)
 5        return; //Base Case
 6    fun1(n - 1);
 7    cout << n << endl;
 8    fun1(n - 1);
 9}
10int main(int argc, char const *argv[])
11{
12    /* code */
13    fun1(3);
14    return 0;
15}
16//Output : 1213121
 1int fn(int n)
 2{
 3    if (n == 1)
 4        return 0;
 5    else
 6        return 1 + fn(n / 2);
 7}
 8//TC: O(floor(logn base2))
 9//SC: O(n/2)
10//fn(16) -> O/p: 4 => (1+f(8)) ->(1+1+f(4)) ->(1+1+1+f(2)) -> (1+1+1+1+f(1))
11//fn(20) -> o/p: 4 =>(1+f(10)) -> (1+1+f(5)) -> (1+1+1+f(2)) -> (1+1+1+1+f(1))
12//fn(19) -> o/p:4 => (1+f(9)) -> (1+1+f(4)) -> ->(1+1+1+f(2)) -> (1+1+1+1+f(1))
13
14//output remains same until we get to next power of 2.

Binary representation of a number(n > 0) using recursion

1int fn(int n)
2{
3    if (n == 0)
4        return;
5    fn(n / 2);
6    cout << n % 2 << endl;
7}
8//TC:

print 1 to N using recursion

1private static void fn(int n) {
2    if (n == 0)
3        return;
4    fn(n - 1);
5    System.out.println(n);
6}//TC; O(n) AS: O(n+1)

print N to 1 using recursion

1private static void fn(int n) {
2    if (n == 0)
3        return;
4    System.out.println(n);
5        n(n - 1);
6}// TC: theta(n)
7//SC: (Auxillary space) : O(n)

Tail Recursion

To understand tail recursion, lets take a closer look at these 2 functions.

1//prints from n to 1
2void fn1() {
3    if(n==0) return;
4    System.out.println(n);
5    fn1(n-1);
6}
7//this function takes lesser time
1//prints from 1 to n
2void fn2(){
3    if(n==0) return;
4    fn2(n-1);
5    System.out.println(n);
6}

Can you guess the reason why would 1st function take lesser time to compile on modern compilers?

If you look at the call stack of fn1()

+__fn1(3)
    |__ 3
    |__fn1(2)
        |__ 2
        |__ fn1(1)
            |__ 1
            |__ fn1(0)

When fn1(0) finishes, control returns back to fn1(1), fn1(1) doesnt have anything to do it finishes immediately. This is where tail recursion comes into picture.

A function is called Tail recursive when the parent function has nothing to do when the child finishes the call.

This is not the case with fn2(3). When fn2(0) returns to its parent fn2(1), it still has got work to do (print the output).

In very simple words

A function is called tail recursive, when the last thing that happens in the function is recursive call and nothing happens after that.

What are the pros of this?

The point is your caller doesn’t have to save the state, generally what happens in recursive calls is, caller’s state is saved then called function is called and once the called function is finished then the caller resumes its function from the same point. We dont need to resume the execution here at all, there’s no point in resuming the execution and thats what the optimisation modern compilers do.

When modern compilers see tail recursive functions they replace the above code with

1void fn1() {
2    //compiler adds this label
3    start:
4        if(n==0) return;
5        System.out.println(n);
6        // and replaces the line fn1(n-1) with below statements
7        n= n-1 ;
8        goto start;
9}

These changes that modern compilers make are called Tail call elimination

Now, the question arises is when given a non tail recursive code, can we convert it tail recursive?

Lets have a look at the below examples.

1//prints from 1 to n
2void fn2(){
3    if(n==0) return;
4    fn2(n-1);
5    System.out.println(n);
6}
1//Tail recursive version of the code
2//initially pass k = 1
3void fn2(int n, int k){
4    if(n==0) return;
5    System.out.println(k);
6    fn2(n-1,k+1);
7}

Can we convert every non tail recursive to tail recursive by adding few parameters?

No. Consider merge sort and quick sort, if you take a closer look at these two algorithms, quick sort is tail recursive and merge sort is not. This is one of the reasons, quick sort is fast.

In case of tree traversals (Inorder,preorder and postorder), you can notice that preorder traversal and inorder traversal are tail recursive, but post order traversal is not, thats why when you are given a problem and if you can choose any of the traversals, you should prefer either inorder or preorder over the postorder.

Is this tail recursive?

1int factorial(int n){
2    if(n==0 || n== 1) return 1;
3    return n * factorial(n-1);
4}

No. The reason is recursion is not the last thing that happens in this function. When you call factorial(n-1) you need to know the result of that function and multiply it with n and then it need to return. Parent call doesn’t finish immediately after the child call, its going to use the result of child call and then multiply the result with n after that its going to return.

Equivalent tail recursive code

1//initially pass k = 1
2int factorial(int n, int k){
3    if(n==0 || n== 1) return k;
4    return factorial(n-1,k*n);
5}

Few problems on recursion worth looking at:

 ds-algo  programming  competitive-programming
↞Intro to React