Analyzing recursive algorithms

  • What is the running time of the following recursive method:

       public static void recurse(int[] A, int a, int b)
       {
           if ( b-a <= 1 )
              doPrimitive();
           else
           {
              doPrimitive();
              recurse(A, a, (a+b)/2);    // First half of array
    	  recurse(A, (a+b)/2, b);    // 2nd half of array
           }
       } 

    Analysis:

        How many times will doPrimitive() be executed 
        when input of the recurse method is b-a = n ?
    
    
        The analysis of recursive algorithms
        require the use of:
    
             recurrence relations....
    

Analyzing recursive algorithms

  • What is the running time of the following recursive method:

       public static void recurse(int[] A, int a, int b)
       {
           if ( b-a <= 1 )
              doPrimitive();
           else
           {
              doPrimitive();
              recurse(A, a, (a+b)/2);    // First half of array
    	  recurse(A, (a+b)/2, b);    // 2nd half of array
           }
       } 

    Assume: C(n) = # times that doPrimitive() is executed when recurse() is called with n:

     Let C(n) = # times that doPrimitive() is executed when input size = n
     
    
    
    
    
    
    
    

Analyzing recursive algorithms

  • What is the running time of the following recursive method:

       public static void recurse(int[] A, int a, int b)
       {
           if ( b-a <= 1 )
              doPrimitive();
           else
           {
              doPrimitive();
              recurse(A, a, (a+b)/2);    // First half of array
    	  recurse(A, (a+b)/2, b);    // 2nd half of array
           }
       } 

    C(0) = 1 because recurse() will execute doPrimitive() one time and terminate:

     Let C(n) = # times that doPrimitive() is executed when input size = n
     
     C(1) = 1    // Because if b-a <= 1, it executes 1 doPrimitive()
    
    
    
    
    
    

Analyzing recursive algorithms

  • What is the running time of the following recursive method:

       public static void recurse(int[] A, int a, int b)
       {
           if ( b-a <= 1 )
              doPrimitive();
           else
           {
              doPrimitive();
              recurse(A, a, (a+b)/2);    // First half of array
    	  recurse(A, (a+b)/2, b);    // 2nd half of array
           }
       } 

    When n > 0, recurse(n) will execute doPrimitive() 1 time and calls recurse( ) twice:

     Let C(n) = # times that doPrimitive() is executed when input size = n
     
     C(1) = 1    // It's the base case
     C(n) = 1 + C(n/2) + C(n/2)    for n > 0
         because:
                recurse(n) will invoke recurse( ) twice with input size n/2
                and by definition, the # times that doPrimitive()
                is executed when input = n/2 is: C(n/2)
    

Analyzing recursive algorithms

  • Therefore:   the recurrence relation that represents the running time is:

     Let C(n) = # times that recurse() is executed when input = n
    
     C(n) = 1 + 2*C(n/2)     for n > 0,   and  C(1) = 1  
    
    
    
       We now proceed to solve this recurrence relation...
       with "telescoping"
    
       YouTube video on telescoping: click here
    
     
    
    
    
    
    
    
    
    
    
    

Analyzing recursive algorithms

  • Solving the recurrence relation: with "telescoping":

     Let C(n) = # times that recurse() is executed when input = n
    
     C(n) = 1 + 2*C(n/2)     for n > 0,   and  C(1) = 1  
    
     C(n) = 1 + 2*C(n/2)   // We start with the general recurrence relation
                           // and reduce it to the base case
      
       
    
      
    
     
    
    
    
    
    
    
    
    
    
    

Analyzing recursive algorithms

  • We can use C(n) = 1 + C(n/2) to obtain a formula for C(n/2) by substituting n ==> n/2:

     Let C(n) = # times that recurse() is executed when input = n
    
     C(n) = 1 + 2*C(n/2)     for n > 0,   and  C(1) = 1  
    
     C(n) = 1 + 2*C(n/2)        ==> C(n/2) = 1 + 2*C(n/4)  
    
      
       
    
      
    
     
    
    
    
    
    
    
    
    
    
    

Analyzing recursive algorithms

  • Substitute the expression for C(n/2) in the formula for C(n):

     Let C(n) = # times that recurse() is executed when input = n
    
     C(n) = 1 + 2*C(n/2)     for n > 0,   and  C(1) = 1  
    
     C(n) = 1 + 2*C(n/2)        ==> C(n/2) = 1 + 2*C(n/4)  
          = 1 + 2*(1 + 2*C(n/4))
          = 1 + 2 + 22*C(n/4)
       
    
      
    
     
    
    
    
    
    
    
    
    
    
    

Analyzing recursive algorithms

  • We can use C(n) = 1 + C(n/2) to obtain a formula for C(n/4) by substituting n ==> n/4:

     Let C(n) = # times that recurse() is executed when input = n
    
     C(n) = 1 + 2*C(n/2)     for n > 0,   and  C(1) = 1  
    
     C(n) = 1 + 2*C(n/2)        ==> C(n/4) = 1 + 2*C(n/8)  
          = 1 + 2*(1 + 2*C(n/4))
          = 1 + 2 + 22*C(n/4)
       
    
      
    
     
    
    
    
    
    
    
    
    
    
    

Analyzing recursive algorithms

  • Substitute the expression for C(n/4) in the formula for C(n):

     Let C(n) = # times that recurse() is executed when input = n
    
     C(n) = 1 + 2*C(n/2)     for n > 0,   and  C(1) = 1  
    
     C(n) = 1 + 2*C(n/2)        ==> C(n/4) = 1 + 2*C(n/8)  
          = 1 + 2*(1 + 2*C(n/4))
          = 1 + 2 + 22*C(n/4)
          = 1 + 2 + 22*(1 + 2*C(n/8))
          = 1 + 2 + 22 + 23*C(n/8)
      
    
     
    
    
    
    
    
    
    
    
    
    

Analyzing recursive algorithms

  • We can see a pattern and can predict the next progression:

     Let C(n) = # times that recurse() is executed when input = n
    
     C(n) = 1 + 2*C(n/2)     for n > 0,   and  C(1) = 1  
    
     C(n) = 1 + 2*C(n/2)        ==> C(n/4) = 1 + 2*C(n/8)  
          = 1 + 2*(1 + 2*C(n/4))
          = 1 + 2 + 22*C(n/4)
          = 1 + 2 + 22*(1 + 2*C(n/8))
          = 1 + 2 + 22 + 23*C(n/8)
          = 1 + 2 + 22 + 23 + 24*C(n/16)
    
     
      (1) get an extra  23
      (2) C(n/8) becomes C(n/16)
    
      (If you don't see the pattern, substitute C(n/8) and work it out)
    
    
    
    
    
    

Analyzing recursive algorithms

  • Repeat these steps until we will get C(1):

     Let C(n) = # times that recurse() is executed when input = n
    
     C(n) = 1 + 2*C(n/2)     for n > 0,   and  C(1) = 1  
    
     C(n) = 1 + 2*C(n/2)        ==> C(n/4) = 1 + 2*C(n/8)  
          = 1 + 2*(1 + 2*C(n/4))
          = 1 + 2 + 22*C(n/4)
          = 1 + 2 + 22*(1 + 2*C(n/8))
          = 1 + 2 + 22 + 23*C(n/8)
          = 1 + 2 + 22 + 23 + 24*C(n/16)
            ....
          = 1 + 2 + 22 + 23 + ... + 2k*C(n/(2k))
                                  where n/(2k) = 1 <==> 2k = n
     
     
      
    
    
    
    
    
    

Analyzing recursive algorithms

  • Substitute C(n/(2k)) with C(1):

     Let C(n) = # times that recurse() is executed when input = n
    
     C(n) = 1 + 2*C(n/2)     for n > 0,   and  C(1) = 1  
    
     C(n) = 1 + 2*C(n/2)        ==> C(n/4) = 1 + 2*C(n/8)  
          = 1 + 2*(1 + 2*C(n/4))
          = 1 + 2 + 22*C(n/4)
          = 1 + 2 + 22*(1 + 2*C(n/8))
          = 1 + 2 + 22 + 23*C(n/8)
          = 1 + 2 + 22 + 23 + 24*C(n/16)
            ....
          = 1 + 2 + 22 + 23 + ... + 2k*C(n/(2k))
                                  where n/(2k) = 1 <==> 2k = n
          = 1 + 2 + 22 + 23 + ... + 2k*C(1)       (with k = log(n))
     
      
    
    
    
    
    
    

Analyzing recursive algorithms

  • Substitute C(1) with 1:

     Let C(n) = # times that recurse() is executed when input = n
    
     C(n) = 1 + 2*C(n/2)     for n > 0,   and  C(1) = 1  
    
     C(n) = 1 + 2*C(n/2)        ==> C(n/4) = 1 + 2*C(n/8)  
          = 1 + 2*(1 + 2*C(n/4))
          = 1 + 2 + 22*C(n/4)
          = 1 + 2 + 22*(1 + 2*C(n/8))
          = 1 + 2 + 22 + 23*C(n/8)
          = 1 + 2 + 22 + 23 + 24*C(n/16)
            ....
          = 1 + 2 + 22 + 23 + ... + 2k*C(n/(2k))
                                  where n/(2k) = 1 <==> 2k = n
          = 1 + 2 + 22 + 23 + ... + 2k*C(1)       (with k = log(n))
          = 1 + 2 + 22 + 23 + ... + 2k            (with k = log(n))
      
    
    
    
    
    
    

Analyzing recursive algorithms

  • Work out the geometric sum:

     Let C(n) = # times that recurse() is executed when input = n
    
     C(n) = 1 + 2*C(n/2)     for n > 0,   and  C(1) = 1  
    
     C(n) = 1 + 2*C(n/2)        ==> C(n/4) = 1 + 2*C(n/8)  
          = 1 + 2*(1 + 2*C(n/4))
          = 1 + 2 + 22*C(n/4)
          = 1 + 2 + 22*(1 + 2*C(n/8))
          = 1 + 2 + 22 + 23*C(n/8)
          = 1 + 2 + 22 + 23 + 24*C(n/16)
            ....
          = 1 + 2 + 22 + 23 + ... + 2k*C(n/(2k))
                                  where n/(2k) = 1 <==> 2k = n
          = 1 + 2 + 22 + 23 + ... + 2k*C(1)       (with k = log(n))
          = 1 + 2 + 22 + 23 + ... + 2k            (with k = log(n))
      
    Let:
        S = 1 + 2 + 22 + 23 + ... + 2k 
       2S =     2 + 22 + 23 + ... + 2k + 2k+1
    
     => S = 2k+1 - 1
    

Analyzing recursive algorithms

  • We found an expression for C(n):

     Let C(n) = # times that recurse() is executed when input = n
    
     C(n) = 1 + 2*C(n/2)     for n > 0,   and  C(1) = 1  
    
     C(n) = 1 + 2*C(n/2)        ==> C(n/4) = 1 + 2*C(n/8)  
          = 1 + 2*(1 + 2*C(n/4))
          = 1 + 2 + 22*C(n/4)
          = 1 + 2 + 22*(1 + 2*C(n/8))
          = 1 + 2 + 22 + 23*C(n/8)
          = 1 + 2 + 22 + 23 + 24*C(n/16)
            ....
          = 1 + 2 + 22 + 23 + ... + 2k*C(n/(2k))
                                  where n/(2k) = 1 <==> 2k = n
          = 1 + 2 + 22 + 23 + ... + 2k*C(1)       (with k = log(n))
          = 1 + 2 + 22 + 23 + ... + 2k            (with k = log(n))
          = 2k+1 - 1  = 2log(n)+1 - 1
    
       
      
    
     
    

Analyzing recursive algorithms

  • We found an expression for C(n):

     Let C(n) = # times that recurse() is executed when input = n
    
     C(n) = 1 + 2*C(n/2)     for n > 0,   and  C(1) = 1  
    
     C(n) = 1 + 2*C(n/2)        ==> C(n/4) = 1 + 2*C(n/8)  
          = 1 + 2*(1 + 2*C(n/4))
          = 1 + 2 + 22*C(n/4)
          = 1 + 2 + 22*(1 + 2*C(n/8))
          = 1 + 2 + 22 + 23*C(n/8)
          = 1 + 2 + 22 + 23 + 24*C(n/16)
            ....
          = 1 + 2 + 22 + 23 + ... + 2k*C(n/(2k))
                                  where n/(2k) = 1 <==> 2k = n
          = 1 + 2 + 22 + 23 + ... + 2k*C(1)       (with k = log(n))
          = 1 + 2 + 22 + 23 + ... + 2k            (with k = log(n))
          = 2k+1 - 1  = 2log(n)+1 - 1
          = 2log(n)*21 - 1  = 2n - 1
       
      
    
     
    

DEMO: demo/13-analysis/Demo11.java