Recursion and the Binary Search*

* explanation borrowed from Programming in C++ by Dale, Weems, and Headington.

 

Recursion is a condition in which a function or sub procedure calls itself.  

The proper definition is: a recursive call is a procedure call in which the procedure being called is the same as the one making the call.

==================

A recursive algorithm reproduces itself with smaller and smaller instances of itself until a solution is found.  The recursive algorithm is implemented by using a procedure that makes recursive calls to itself.

==================

Non-recursive example

The common power function (usually implemented in VB with the ^ operator) receives two integers, x and n (where n > 0) and computes xn.  The iterative (non-recursive) approach is to multiply repeatedly by x.  Because the number of iterations is known in advance, a count-controlled loop is appropriate.  The loop counts down to 0 from the initial value of n.  For each iteration of the loop, x is multiplied by the previous product.

'**************************************************************************
'*  This function uses a non-recursive approach to compute
'*  x to the n power
'**************************************************************************
Private Function power (Byval x As Integer, ByVal n As Integer) As Integer

    Dim result as Integer

    result = 1
    While (n > 0)
        result = result * x
        n = n - 1
    Wend
    power = result
End Function 

==================

Recursive version

In the power function above, the formula for xn is 

        xn = x * x * x * x* ... * x

The formula could also be written as 

        xn = x * (x * x * x* ... * x)  
                            (n-1) times

or even as 

        xn = x * x * (x * x * x* ... * x)  
                                (n-2) times

In fact, the formula could be written concisely as

        xn = x * xn-1

The definition of xn is a classic recursive definition--in other words a definition given in terms of a smaller version of itself.

xn is defined in terms of multiplying x times xn-1xn-1  is defined as x times xn-2.   xn-2 is defined as x times xn-3xn-3  is defined as x times xn-4, etc.  In this example, "in terms of smaller versions of itself" means that the exponent is decremented each time.

The process stops only when we reach a case in which we know the answer without resorting to a recursive definition.  In this example, it is the case where n = 0, since x0 = 1

The case for which an answer is explicitly known is called the base case.  

The case for which the solution is expressed in terms of a smaller version of itself is called the recursive or general case

A recursive algorithm is an algorithm that expresses the solution in terms of a call to itself, a recursive call.

A recursive algorithm must terminate, that is, it must have a base case.

'**************************************************************************
'*  This function uses a recursive approach to compute
'*  x to the n power
'**************************************************************************
Private Function power (Byval x As Integer, ByVal n As Integer)
As Integer

    If ( n = 0) Then
        power = 1                                ' base case
    Else
        power = x * power(x, n-1)    ' recursive call
   
EndIf
End Function 

Each recursive call to power can be thought of as creating a completely new copy of the function, each with its own copies of the parameters x and n.  The value of x remains the same for each version of power, but the value of n decreases by one for each call until it becomes 1.

==================

Trace

Here is a trace of the function, with x = 2 and n = 3.

      

Call 1: power is called with the arguments x and n initialized to 2 and 3 respectively.  Because n is not equal to 1, power is called recursively with x and n-1 as parameters.  Execution of Call 1 pauses until an answer is sent back from this recursive call.

Call 2: x is equal to 2 and n is equal to 2.  Because n is not equal to 1, the function power is called again, this time with x and n-1 as parameters.  Execution of Call 2 pauses until an answer is sent back from this recursive call.

Call 3: x is equal to 2 and n is equal to 1.  Because n is not equal to 0, the function power is called again, this time with x and n-1 as parameters.  Execution of Call 3 pauses until an answer is sent back from this recursive call.

Call 4: x is equal to 2 and n is equal to 0.  Because n is equal to 0, the value of 1 is returned.  This call to the function has finished executing, and the function return value, which is 1, is passed back to the place in the statement from which the call was made.

Call 3: This call to the function can now complete the statement that contained the recursive call, because the recursive call has returned.  Call 4's return value (which is 1) is multiplied by x.  This call to the function has finished executing and the function return value (which is 2) is passed back to the place in the statement from which the call was made.

Call 2: This call to the function can now complete the statement that contained the recursive call, because the recursive call has returned.  Call 3's return value (which is 2) is multiplied by x.  This call to the function has finished executing and the function return value (which is 4) is passed back to the place in the statement from which the call was made.

Call 1: This call to the function can now complete the statement that contained the recursive call, because the recursive call has returned.  Call 2's return value (which is 4) is multiplied by x.  This call to the function has finished executing and the function return value (which is 8) is passed back to the place in the statement from which the call was made.  Because the first call (the nonrecursive call in some other routine) has now completed, this is the final value for function power.

==================

Writing Recursive Algorithms

  1. Understand the problem.
  2. Determine the base case(s).
  3. Determine the recursive case(s).

==================

Recursive Algorithms with Data Structures

In the definition of a recursive algorithm, it states that there are two cases: the recursive or general case, and the base case.

In the general case the parameter can be expressed in terms of a smaller value each time.

When data structures are used, the recursive case is often in terms of a smaller structure rather than a smaller value.  The base case occurs when there are no values left to process in the structure.

==================

This example presents a recursive algorithm for printing the contents of a one-dimensional array of n elements.

printArray:
If more elements
    Print the value of the first element
    printArray of n-1 elements

The recursive case is to print the values in an array that is one element smaller; that is, the length of the array decreases by 1 with each recursive call.  

The base case is when the length of the array becomes 0--that is, there are no more elements to print.

The parameters must include the index of the first element (the one to be printed).  

The last element in the array has been printed (that is, the length of the array to be printed is 0) when the index of the next element to be printed is greater than the index of the last element in the array. Therefore the index of the last element must also be passed as a parameter. (Is the index of the last element always the same as UBound?)

In the sample code, the indexes will be called first and last, and the procedure terminates when first is greater than last.

Private Sub printArray (ByRef list( ) as Integer, _
                                            ByVal first as Integer, _
                                            ByVal last as Integer)
    If (first <= last) Then                        ' recursive case
        Print list(first)
        printArray(list, first+1, last)
    EndIf                                                   ' empty else is the base case
End Sub

A trace of printArray invoked on the following array is shown below:

Notice that once the deepest call (the call with the highest number) was reached, each of the calls before it returned without doing anything..

When no statements are executed after the return from the recursive call to the function, the recursion is known as tail recursion.  Tail recursion often indicates that the problem could be solved more easily using iteration.  This example was used because it made the problem easy to visualize; in practice an array should be printed iteratively.

Note also that the array gets smaller with each recursive call.

==================

A Recursive Binary Search

The binary search algorithm presented in the search notes is better written as a recursive function.

A binary search requires that an array be in sorted order--either ascending or descending.

In the recursive binary search, we first compare the key with the item in the middle position of the array. If there's a match, we can return immediately. If the key is less than the middle key, then the item sought must lie in the lower half of the array; if it's greater then the item sought must lie in the upper half of the array. So we repeat the procedure on the lower (or upper) half of the array.

The binary search is recursive: it determines whether the search key lies in the lower or upper half of the array, then calls itself on the appropriate half. 

'*****************************************************************************************
'* Returns index if found, otherwise returns -1
'*****************************************************************************************
Private Function recBinarySearch ( ByRef mArray( ) As Integer, _
                                                                   ByVal searchKey As Integer, _
                                                                   ByVal low As Integer, _
                                                                   ByVal high As Integer) As Integer
    Dim middle As Integer

    If high < low Then                                                ' searchKey not in mArray
           
recBinarySearch = -1                                  ' base case #1
    Else
            middle = (low + high) \ 2
            If (searchKey = mArray(middle)) Then
                   
recBinarySearch = middle               ' base case #2
            ElseIf searchKey < mArray(middle) Then  
                    
recBinarySearch = recBinarySearch(mArray, searchKey, low, middle-1)
            Else ' searchKey > mArray(middle) 
                    
recBinarySearch = recBinarySearch(mArray, searchKey, middle+1, high)
            EndIf
    EndIf
End Function

==================

The Choice between Iterative and Recursive Solutions

Recursion and iteration are alternative ways of expressing repetition in a program.

Iteration is generally more efficient in terms of both execution speed and memory usage.

The choice between recursion and iteration depends on the nature of the problem being solved.  

==================

Summary

A recursive algorithm is expressed in terms of a smaller instance of itself.  It must include a recursive case, for which the algorithm is expressed in terms of itself, and a base case, for which the algorithm is expressed in non-recursive terms.

In many recursive problems, the smaller instance refers to a numeric parameter that is being reduced with each call.  In other problems, the smaller instance refers to the size of the data structure being manipulated.  The base case is the one in which the size of the problem (value or structure) reaches a point where an explicit answer is known.

==================