Runtime anaylsis of the Quick Sort algorithm

  • Running time of the Quick Sort algorithm:

    • The running time of Quick Sort depends on how partition( ) splits up the input array

  • Recall the partition(A, s, e) algorithm:

      Input:     [ 5   1   7   2   8   9   3   6 ]
    
      Result:    [ 1   2   3   5   7   8   9   6 ]
                   <------->       <------------>
                      < 5             >= 5
    

  • Important fact:

    • The running time of Quick Sort depends on how large the 2 array halves are !

Recurrence relation for the running time of the Quick Sort algorithm

  • Suppose the partition(A, s, e) algorithm partitions the input as follows:

                   <-------------------  n  ------------------->
      Input:     [ P  ..  ..  ..  ..  ..  ..  ..  ..  ..  ..  .. ]
    
      Result:    [ .. .. .. .. .. ..   P  .. .. .... .. .. .. .. ]
                   <-----  k  ----->      <-----  n-k-1  ------>
    

  • Recall the Quick Sort algorithm:

     public static > void sort(T[] A, int s, int e)  // n = e-s
     {
         if ( e - s <= 1 )       // Base case
             return;            
    
         int pivotLoc = partition(A, s, e); // Running time = n
    
         sort(A, s, pivotLoc);   // Input size = k
         sort(A, pivotLoc+1, e); // Input size = n-k-1
     }
    

  • Running time T(n) for input size n:   T(n) = T(k) + T(n-k-1) + n

The best case running time of Quick Sort

  • The best case running time of the Quick Sort algorithm happens when:

    • The partition( ) algorithm always divides the input array into 2 equal halves

  • In this case, we have:

      T(n) = T(k) + T(n-k-1) + n         k = n/2
           = T(n/2) + T(n/2-1) + n
           ~= 2 T(n/2) + n
    

    which is the similar recurrence relation as the one in Merge Sort

  • We can solve it with telescoping and the result is:

      Best case running time = nlog(n)   
    
      which is: O(nlog(n))
    

The worst case running time of Quick Sort

  • The worst case running time of the Quick Sort algorithm happens when:

    • The partition( ) algorithm always divides the input array into (1) array containing the pivot and (2) an array with n-1 elements

  • In this case, we have:

      T(n) = T(0) + T(n-1) + n         
           =   0  + T(n-1) + n    =  T(n-1) + n
    

  • Solve the recurrence relation with telescoping:

     T(n)  =  T(n-1)  +  n                  T(n-1) = T(n-2) + (n-1)
           =  T(n-2)  +  (n-1) + n          T(n-2) = T(n-3) + (n-2)
           =  T(n-3)  +  (n-2) + (n-1) + n
           =  ...
           =  T(1)    +  2 + 3 + 4 + ... + (n-1) + n       T(1) = 0
           =    0     +  2 + 3 + 4 + ... + (n-1) + n (Triangular sum)
           =  n(n+1)/2 - 1
           =  O(n2)   <--- Worst case running time
    

Example that makes Quick Sort achieve the worst case running time

  • When Quick Sort is used to sort a sorted array, it will result in the worst case running time of O(n2)

    Example:

        A[]  =  [ 1    2    3    4    5    6    7    8  ... ]
                  ^
                  |
                pivot
    
     After partition(A, 0, n):
    
        A[]  =  [ 1    2    3    4    5    6    7    8  ... ]
                       ^
                       |
                     pivot
    
     And so on...
    

  • Because the pivot is always the smallest value, partition( ) will always produce:

    1. An array containing the pivot
    2. An array containing the other n-1 elements

Commonly used practice with Quick Sort

  • Commonly used practice when using Quick Sort:

    • Shuffle the input array randomly

      (To ensure partition( ) will not always find the pivot as the first element)

    • Use Quick Sort of the shuffled input array

  • Note:

    • Quick Sort will achieve the average running time performance with a randomized input array

    We will study the average running time of Quick Sort next

The average case running time of Quick Sort

  • Recall the recurrence relation for the running time of Quick Sort is:

     T(n) = T(k) + T(n-k-1) + n  where k = final pos of the pivot
    

  • Depending on the value of k, the recurrence relation for T(n) are:

      T(n) = T(0) + T(n-1) + n     if k = 0       
      T(n) = T(1) + T(n-2) + n     if k = 1
      ...
      T(n) = T(n-1) + T(0) + n     if k = n-1
    

  • Each value of k is equally likely to occur, so we can take the average by:

      T(n) = T(0) + T(n-1) + n     if k = 0       
      T(n) = T(1) + T(n-2) + n     if k = 1
      ...
      T(n) = T(n-1) + T(0) + n     if k = n-1   (average = sum/n)
    ----------------------------------------------------------------
     nT(n) = (T(0) + ... + T(n-1)) + (T(n-1) + ... + T(0)) + n*n
     nT(n) = 2T(0) + 2T(1) + ... + 2T(n-1) + n*n
      T(n) = 2/n*T(0) + 2/n*T(1) + ... + 2/n*T(n-1) + n 
    

  • Solution:   T(n) = 2(n+1) Hn ~= 2(n+1)lg(n+1) = O(nlog(n))

Solving the recurrence relation for the average case

  • The recurrence relation for the average case running time of Quick Sort is:

      T(n) = 2/n*T(0) + 2/n*T(1) + ... + 2/n*T(n-1) + n
    
    
    
    
    
    
       I will show you the solution procedure
       if time permits....
    
    
    
    
    
    
    
    
    
    
    

  • We need a base case (termination condition) for the recurrence relation....

Solving the recurrence relation for the average case

  • Quick Sort on an array of size = 1 will return immediately:

      T(n) = 2/n*T(0) + 2/n*T(1) + ... + 2/n*T(n-1) + n
      T(1) = 1
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    

  • We assign the cost = 1 for running time of the test e - s <= 1

Solving the recurrence relation for the average case

  • We first multiply the general equation by n:

      T(n) = 2/n*T(0) + 2/n*T(1) + ... + 2/n*T(n-1) + n
      T(1) = 1
    
      nT(n)       = 2*T(0) + 2*T(1) + ... + 2*T(n-2) + 2*T(n-1) + n2
     
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    

  • Now substitute: n = n-1 in the equation...

Solving the recurrence relation for the average case

  • If we substitute n = n-1 , we will get a similar equation:

      T(n) = 2/n*T(0) + 2/n*T(1) + ... + 2/n*T(n-1) + n
      T(1) = 1
    
      nT(n)       = 2*T(0) + 2*T(1) + ... + 2*T(n-2) + 2*T(n-1) + n2
      (n-1)T(n-1) = 2*T(0) + 2*T(1) + ... + 2*T(n-2) + (n-1)2
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    

  • Subtract the 2 equations are simplify:

Solving the recurrence relation for the average case

  • If we substitute n = n-1 , we will get a similar equation:

      T(n) = 2/n*T(0) + 2/n*T(1) + ... + 2/n*T(n-1) + n
      T(1) = 1
    
      nT(n)       = 2*T(0) + 2*T(1) + ... + 2*T(n-2) + 2*T(n-1) + n2
      (n-1)T(n-1) = 2*T(0) + 2*T(1) + ... + 2*T(n-2) + (n-1)2
    
      nT(n) - (n-1)T(n-1) = 2*T(n-1) + n2 - (n-1)2
                          = 2*T(n-1) + n2 - (n2 - 2n + 1)
    		      = 2*T(n-1) + n2 -  n2 + 2n - 1 
    		      = 2*T(n-1) + 2n - 1 
    
    
    
    
    
    
    
    
    
    

     

Solving the recurrence relation for the average case

  • Consolidate the term T(n-1) that appears on both side of the equation:

      T(n) = 2/n*T(0) + 2/n*T(1) + ... + 2/n*T(n-1) + n
      T(1) = 1
    
      nT(n)       = 2*T(0) + 2*T(1) + ... + 2*T(n-2) + 2*T(n-1) + n2
      (n-1)T(n-1) = 2*T(0) + 2*T(1) + ... + 2*T(n-2) + (n-1)2
    
      nT(n) - (n-1)T(n-1) = 2*T(n-1) + n2 - (n-1)2
                          = 2*T(n-1) + n2 - (n2 - 2n + 1)
    		      = 2*T(n-1) + n2 -  n2 + 2n - 1 
    		      = 2*T(n-1) + 2n - 1 
    
      nT(n) = (n-1+2)T(n-1) + 2n - 1
      
    
    
    
    
    
    
    

     

Solving the recurrence relation for the average case

  • Simplify:

      T(n) = 2/n*T(0) + 2/n*T(1) + ... + 2/n*T(n-1) + n
      T(1) = 1
    
      nT(n)       = 2*T(0) + 2*T(1) + ... + 2*T(n-2) + 2*T(n-1) + n2
      (n-1)T(n-1) = 2*T(0) + 2*T(1) + ... + 2*T(n-2) + (n-1)2
    
      nT(n) - (n-1)T(n-1) = 2*T(n-1) + n2 - (n-1)2
                          = 2*T(n-1) + n2 - (n2 - 2n + 1)
    		      = 2*T(n-1) + n2 -  n2 + 2n - 1 
    		      = 2*T(n-1) + 2n - 1 
    
      nT(n) = (n+1)T(n-1) + 2n - 1
     
    
    
    
    
    
    
    

     

Solving the recurrence relation for the average case

  • We obtain a simpler recurrence relation for T(n):

      T(n) = 2/n*T(0) + 2/n*T(1) + ... + 2/n*T(n-1) + n
      T(1) = 1
    
      nT(n)       = 2*T(0) + 2*T(1) + ... + 2*T(n-2) + 2*T(n-1) + n2
      (n-1)T(n-1) = 2*T(0) + 2*T(1) + ... + 2*T(n-2) + (n-1)2
    
      nT(n) - (n-1)T(n-1) = 2*T(n-1) + n2 - (n-1)2
                          = 2*T(n-1) + n2 - (n2 - 2n + 1)
    		      = 2*T(n-1) + n2 -  n2 + 2n - 1 
    		      = 2*T(n-1) + 2n - 1 
    
      nT(n) = (n+1)T(n-1) + 2n - 1
       T(n) = (n+1)/n * T(n-1) + 2 - 1/n   
    
     
    
    
    
    
    

     

Solving the recurrence relation for the average case

  • In order to solve this recurrence relation, we apply a different telescoping technique:

      T(n) = 2/n*T(0) + 2/n*T(1) + ... + 2/n*T(n-1) + n
      T(1) = 1
    
      nT(n)       = 2*T(0) + 2*T(1) + ... + 2*T(n-2) + 2*T(n-1) + n2
      (n-1)T(n-1) = 2*T(0) + 2*T(1) + ... + 2*T(n-2) + (n-1)2
    
      nT(n) - (n-1)T(n-1) = 2*T(n-1) + n2 - (n-1)2
                          = 2*T(n-1) + n2 - (n2 - 2n + 1)
    		      = 2*T(n-1) + n2 -  n2 + 2n - 1 
    		      = 2*T(n-1) + 2n - 1 
    
      nT(n) = (n+1)T(n-1) + 2n - 1
       T(n) = (n+1)/n * T(n-1) + 2 - 1/n   with  T(1) = 1
    
     Trick: divide both side by  n+1
    
       T(n)/(n+1) = 1/n * T(n-1) + 2/(n+1) - 1/(n+1)n
                  = T(n-1)/n + 2/(n+1) - 1/(n+1)n
    
    

     

Solving the recurrence relation for the average case

  • We can (finally) solve the simplified recurrence relation with telescoping:

     T(n)/(n+1)   = T(n-1)/n     + 2/(n+1) - 1/(n+1)n       with  T(1) = 1
    
    
    
    
         
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    

Solving the recurrence relation for the average case

  • Substitue n = n - 1 the expression for T(n)/(n+1) and obtain the following equation:

     T(n)/(n+1)   = T(n-1)/n     + 2/(n+1) - 1/(n+1)n       with  T(1) = 1
    
     T(n-1)/n     = T(n-2)/(n-1) + 2/n     - 1/n(n-1)
    
    
         
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    

Solving the recurrence relation for the average case

  • Substitue n = n - 2 the expression for T(n)/(n+1) and obtain the following equation:

     T(n)/(n+1)   = T(n-1)/n     + 2/(n+1) - 1/(n+1)n       with  T(1) = 1
    
     T(n-1)/n     = T(n-2)/(n-1) + 2/n     - 1/n(n-1)
    
     T(n-2)/(n-1) = T(n-3)/(n-2) + 2/(n-1) - 1/(n-1)(n-2)
         
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    

Solving the recurrence relation for the average case

  • Substitue n = n - 3 the expression for T(n)/(n+1) and obtain the following equation:

     T(n)/(n+1)   = T(n-1)/n     + 2/(n+1) - 1/(n+1)n       with  T(1) = 1
    
     T(n-1)/n     = T(n-2)/(n-1) + 2/n     - 1/n(n-1)
    
     T(n-2)/(n-1) = T(n-3)/(n-2) + 2/(n-1) - 1/(n-1)(n-2)
         
     T(n-3)/(n-2) = T(n-4)/(n-3) + 2/(n-2) - 1/(n-2)(n-3)
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    

Solving the recurrence relation for the average case

  • And so on... telescope until we reach the base case T(1):

     T(n)/(n+1)   = T(n-1)/n     + 2/(n+1) - 1/(n+1)n       with  T(1) = 1
    
     T(n-1)/n     = T(n-2)/(n-1) + 2/n     - 1/n(n-1)
    
     T(n-2)/(n-1) = T(n-3)/(n-2) + 2/(n-1) - 1/(n-1)(n-2)
         
     T(n-3)/(n-2) = T(n-4)/(n-3) + 2/(n-2) - 1/(n-2)(n-3)
    
      ....
    
     T(2)/3       = T(1)/2       + 2/3     - 1/(3*2)
    
    
    
    
    
    
    
    
    
    
    
    

Solving the recurrence relation for the average case

  • Add all the equations and obtain a new equation:

     T(n)/(n+1)   = T(n-1)/n     + 2/(n+1) - 1/(n+1)n       with  T(1) = 1
    
     T(n-1)/n     = T(n-2)/(n-1) + 2/n     - 1/n(n-1)
    
     T(n-2)/(n-1) = T(n-3)/(n-2) + 2/(n-1) - 1/(n-1)(n-2)
         
     T(n-3)/(n-2) = T(n-4)/(n-3) + 2/(n-2) - 1/(n-2)(n-3)
    
      ....
    
     T(2)/3       = T(1)/2       + 2/3     - 1/(3*2)
    
    Add all the equations:
    
     T(n)/(n+1) + T(n-1)/n + T(n-2)/(n-1) + ... + T(2)/3
    
            =     T(n-1)/n + T(n-2)/(n-1) + ... + T(2)/3 + T(1)/2
                + 2/(n+1) + 2/n + 2/(n-1)  + ... + 2/3
    	    - 1/(n+1)n - 1/n(n-1) - 1/(n-1)(n-2) - ... 1/(3*2)
    
    
    
    

Solving the recurrence relation for the average case

  • Cancel the common terms and simplify:

     T(n)/(n+1)   = T(n-1)/n     + 2/(n+1) - 1/(n+1)n       with  T(1) = 1
    
     T(n-1)/n     = T(n-2)/(n-1) + 2/n     - 1/n(n-1)
    
     T(n-2)/(n-1) = T(n-3)/(n-2) + 2/(n-1) - 1/(n-1)(n-2)
         
     T(n-3)/(n-2) = T(n-4)/(n-3) + 2/(n-2) - 1/(n-2)(n-3)
    
      ....
    
     T(2)/3       = T(1)/2       + 2/3     - 1/(3*2)
    
    Add all the equations:
    
     T(n)/(n+1) + T(n-1)/n + T(n-2)/(n-1) + ... + T(2)/3 
    
            =     T(n-1)/n + T(n-2)/(n-1) + ... + T(2)/3 + T(1)/2  (Small)
                + 2/(n+1) + 2/n + 2/(n-1)  + ... + 2/3
    	    - 1/(n+1)n - 1/n(n-1) - 1/(n-1)(n-2) - ... 1/(3*2) (Small)
    
     T(n)/(n+1) ~  2(1/(n+1) + 1/n + 1/(n-1)  + ... + 1/3 + 1/2 + 1)
                =  2log(n+1)  
    

    Therefore: T(n) = 2(n+1)log(n+1) or O(nlog(n)