Recursion Primer Using C++: Part 2

1. Introduction

In the first part of this article, we studied different types of recursion and their implementation using C++ [5]. We studied recursion from two different dimensions; first, it is either performed at compile time or at run time, second, we discussed five different types of recursion. Now we are going to study recursion again with one more dimension.

2. Recursion Type

Recursion can also be classified by Structure Recursion and Generative Recursion. The main difference between these is, Generative recursion usually decomposes the original problem into sub problems to solve it. For example, to calculate the factorial of a given number, we calculate the factorial of a number one less than the given number and keep doing this until we reach the termination condition. In the case of binary search, we divided the given array into two pieces and run the binary search on that. In this case, we eliminate half of the elements during every recursive call until we either found the required element or reach a point where we have only one element left. Now if that element is required then we found it at after Log2 (n) comparison (i.e. worst case); where “n” is the total number of elements in an array.

On the other hand Structure recursion usually performs data rather than decomposing the problem into smaller pieces. We can store data in recursive structures such as Binary Tree and in that case it would be natural to use recursion to perform operations on it. Structure recursion is not only limited to recursive data structure, but it would be very handy in some linear structures such as Link List.

In my previous article [5] we studied two dimensions of recursion i.e. recursion types such as linear, tail, binary, etc and whether it happens at compile time or at run time. Here we added one more dimension in it i.e. structure recursion and generative recursion, and now we are going to study them.

First we are going to study the compile time and run time variant of structure and generative programming. For the compile time version we are again going to study the meta-programming template. In fact, we have already studied the generative compile time recursion in the previous article. Almost all of the template meta-programming examples given in the previous article are also examples of generative recursion.

We can define this relationship with simple block diagrams. This diagram is very much similar to one we have already seen in previous article.

The syntax of template meta-programming is still most confusing to even an experienced C++ developer until he/she gets his/her hands dirty with it a little bit. Therefore, I decided to use the same example for compile time and run time recursion for an easier comparison between these two.

2.1. Run Time Structure Recursion

I decide to select the link list as an example to discuss runtime and compile time structure recursion. The main reason for this is, it is very easy to implement and understand.

Here is a simple structure of a node of link list. Although this node contains only one data element, but it can be easily extend to store as much information as you like.

// Node for Single Link Liststruct Node
{
intvalue;
Node* next;
};

Before we actually start playing with link lists it would be helpful to make a function to add items in the link list. Here is one simple function to add the values in the link list. This implementation is not the world's best implementation of add values in the link list (what will be the case when there isn’t enough memory to allocate a new node?) but for our purpose this can be a good candidate for proof of concept.

Now we have enough tools to start playing with it. Here are a few examples of traversing a Single Link List recursively. If you take a close look then you may notice that all of these examples are also types of tail recursion, which we already studied in the previous episode. We can even call these examples “Runtime time, Structure, Tail Recursion.”

2.2. Compile Time Structure Recursion

Here we are going to look at the Compile time version of all of these programs. The compile time version of these examples are even more fun, because here we are going to create a data structure at compile time, i.e. Compile Time Data Structure. Compile Time Data Structure is in fact not a new concept in C++. Further information about it can be found in [1][3]. Here is the compile time equivalent of what we already studied in the previous example.

Here we need one more structure to indicate the termination condition of the link list. You can also say it is an end marker. In the run time version we don’t need any such concept because in that case we simply check the value of next field in the node. If its value is NULL then it means that it is the last node of the link list.

However, in the case of a meta-programming template, we have to do template specialization (or partial template specialization) to stop the recursion. We can do template specialization for any specific type or for any specific value. Here we can’t do template specialization on a value, because the second template parameter is type. Therefore, we have to create one new type to stop the recursive instantiation of template. The name of the end marker can be anything and it can store whatever you like. In our example it would be sufficient to make one empty structure to create a new type as an end marker.

Now the fun part begins. Here are few example of traversing static link list at compile time using recursive template instantiation. In first example we are going to calculate the length of the static link list, in second we add the items of the static link list and in third we multiply the items in the static link list. These examples are compile-time version of the same thing we studied in previous section. Therefore we can say these examples are “Compile time, Structure, Tail Recursion”.

2.3. Run Time Generative Recursion

Here is an example of Generative Runtime recursion. Suppose we want to calculate number of digits in a given number then we can calculate it by dividing the original number by 10 until it becomes less than 10 and count all the divisions. For example, if the given number is 34253 then the first time when we divide this number by 10 we will get 3425, (here we are storing the result in integer variable, therefore, we don’t care about the floating point values). Subsequently we will get 342, 34 and 3.

We can implement this problem more than one way; the most obvious ones are looping and recursion. Even in recursion we can use different recursive techniques to do this. Here we are going to solve this problem with tail recursion.

2.4. Compile Time Generative Recursion

As we have seen in the previous article that most of the time when we are discussing recursion we usually also mention the mathematical equation too, but this is not the case in the previous example. There can be several other good mathematical examples to discuss generative and structure recursion with compile time and run time variant. But there is one very good reason to select that example. This example has more than one termination condition to stop recursion. As we have seen in the run time version of this problem, the termination condition is when the input number is less than 10 no matter if it is 0, 1, 2 or any other number less than 10.

Here is our first attempt to solve this problem using a naïve approach. Here we created a specialization class for all the termination condition.

There are two major problems with this version. The first one is, of course, writing almost the same code for every condition. The second one is even more severe. This program doesn’t truly reflect the run time counterpart. Here we assumed that the only positive numbers are less than 10 and completely ignore the negative numbers. This program will produce incorrect results if we are going to use this with a negative number.

This problem is very serious to handle, because all negative numbers are less than 10 and to make this program work on all negative numbers we have to make infinite specialization classes.

Here is our first approach to solving both problems. In the next program we are going to use the preprocessor macro to automatically generate the template specialization classes for us. At the same time we also try to attack the infinite template specialization classes by restricting it to only 19 cases (9 for positive numbers, 9 for negative numbers and one for zero). Because we know that if we keep dividing any number (positive or negative) by 10 then eventually we will get the single digit number in the range of -9 to 9.

This program still has one problem. In the above problem we came across with a problem with 19 termination conditions, but what will the situation be when our termination condition is less than 1000 or 100,000? Although we are not going to write the specialization class 100,000 times, but we still have to use the preprocessor macro 100,000 times. In other words, indirectly, we are going to introduce that much template specialization with the help of the preprocessor.

In our next attempt we are going to solve this problem by introducing one more template class and one more non type template parameter. Now we are going to introduce one more template class to check our condition, which can be even more complex than our example.

There are two main advantages of this technique; first, we are no longer using a preprocessor macro to generate lots of template specialization classes for us. And second, we can handle almost any termination condition without creating tons of new specialization classes.

In the last version of the program we just did some cosmetic changes to make it easier for the caller to call it by using default value of template parameter. We almost always passed one as a second parameter and the return value of IsSingleDigit as a third parameter, therefore we use this as a default parameter in our CountDigit structure.

In the last section of the first part of this article[5] we discussed template recursion with run time and compile time components of a program. We made a program to generate the simple class to make an n dimension array using template recursion. In that program and all of the compile time recursion programs we used the non-type template argument. There is one very important feature of the template meta-programming with the help of type, when instead of writing a program for one specific type, we can generalize it for any type that qualify the concept used in the program.

3. Linear Recursion

Linear recursion is the simplest form of recursion and perhaps the most commonly used recursion. In this recursion one function simply calls itself until it reaches the termination condition (also known as base condition); this process is known as winding. After calling the termination condition the execution of the programs returns to the caller; this is known as unwinding.

3.1. Structured Linear Recursion

Link list, again, perhaps the most commonly used data structure for linear recursion. We have already studied link list in previous section, this time we are going to print the elements in the link list. We can do the same thing with looping, i.e. traverse each elements of the link list until reach the element with next address equal to NULL. Here is the simple recursive implementation of printing all the elements of link list.

3.2. Generative Linear Recursion

If we are supposed to make a function that will create and populate a vector (or any other data structure) with some predefined data then we can solve this in either with looping or with recursion. Here is the recursive version of the function.

In this function we are inserting the low value in the vector then incrementing it and calling it recursively until the low value becomes larger than the higher value. Here is the usage of this function.

This might not be a good and interesting example of generative linear recursion. Let’s take a look at the variation of Josephus Problem discussed in Concrete Mathematics book[4].

“During the Jewish-Roman war, he [Josephus] was among a band of 41 Jewish rebels trapped in a cave by the Romans. Preferring suicide to capture, the rebels decided to form a circle and, proceeding around it, to kill every third remaining person until no one was left.” [4]

“In our variation, we start with n people numbered 1 to n around a circle, and we eliminate every second remaining person until only one survives” [4].

Here is the mathematical formula to solve this problem.

</img />

One more reason to select this example was that from the equation there isn’t any obvious solution for any value of n greater than 1. In other words we can’t see what would function to in case of J(n). If we take a look at this closely then these equations just show two special cases, one for even numbers and other for odd numbers in addition to termination condition. Here is the simple implementation of the Modified Josephus Problem in C++ using recursion.

4. Tail Recursion

Tail recursion is a specialized form of linear recursion where the recursive function called is usually the last call of the function. This type of recursion is usually more efficient because the smart compiler will automatically convert this recursion into loop to avoid nested function calls. Because a recursive function call is usually the last statement of a function, therefore, there isn’t any work done during the unwinding phase, instead of this they simply return the value of a recursive function call. In other words, a recursive function doesn’t store any state information, and during the unwinding phase just returns the value.

4.1. Structure Tail Recursion

Let’s take an example of link list again. If we want to find the maximum value in the link list we can do it with either looping or with recursion. Here is an example to find the maximum value in the link list using tail recursion.

This function calls itself again and again recursively and passes the input parameter after dividing by 10 until it is equal to 0. At the same time this function create a reverse number and pass it as a second parameter of itself. Then during the unwinding phase just return the number as it is.

5. Mutual Recursion

Mutual recursion is also known as indirect recursion. In this type of recursion two or more than two functions call each other in circular way. This is the only way of doing recursion if a programming language doesn’t allow calling functions recursively. The termination condition in this recursion can be in one or all functions.

5.1. Structure Mutual Recursion

Let’s take a look at the problem to check that the given string is palindrome or not. The palindrome string is a string that can be the same whether read from left or right, such as “Hannah”, “Tenet”, “Civic,” and “Madam” etc. By definition, one letter word is also a palindrome, therefore this function returns true if the length of the given string is only one. This function calls another helper function named “helperPlaindrome,” which was checking the first and last letter of the string and call isPlaindrome again.

Here is the simple implementation of checking if string is a palindrome or not using Mutual recursion.

5.2. Generative Mutual Recursion

In the previous article we have already seen two examples of mutual recursion. There are a few more interesting examples of mutual recursion defined in this paper [2]. We have already discussed the Fibonacci numbers. The actual problem of Fibonacci numbers is defined as a pair of rabbits and we are supposed to calculate the total number of rabbits after “n” months. These are the conditions defined in the problem.

During the first month there is only one pair or one rabbit and we're supposed the take one month to produce one more pair of babies while not dying. In addition, they always produce a pair of one male and one female rabbit. If we count the total number of pairs during every month then these are Fibonacci numbers.

Now instead of calculating the number of pairs every months if we want to calculate number of Adult pairs (mature paired) and number of Baby pairs then we can calculate it with the following formulae.

</img />

</img />

This is a type of mutual recursion. In this example, the “Baby” function calls the “Adult” function and vice versa. Here is the simple implementation of this problem.

6. Binary Recursion

In binary recursion the recursive function calls itself twice not once. This type of recursion is very useful as some data structures like traversing a tree in prefix postfix or infix order, generating Fibonacci numbers etc.

Binary recursion is a specific form of exponential recursion, where one function calls the recursive function more than once (in case of binary two). In other words, recursive functions calls exponentially in this type of recursion.

6.1. Structure Binary Recursion

A Binary Tree is a typical example of Binary Recursion. In a Binary tree every node has two child nodes and every child node may contain two children. Recursion is a natural choice here, because of the recursive nature of the data structure. Here is the simple code to add items in a Binary tree and display it.

At first it looks that AddItem is using Binary recursion, because it calls itself twice, but that is not a case. For any given value, the AddItem function calls itself only once, either to insert items in right child or in left child. PrintTree is a true Binary recursive function, because it calls itself twice for every node, to traverse both left and right children.

As we have already discussed, Binary Recursion is just a special case of Exponential Recursion, because in Exponential recursion a recursive function may call itself more than twice. Its example may be a Generalized Tree, not a Binary Tree, where any node of a Tree may contain any number of children. In real life we can see this as a directory structure of our operating system. Here is the simplest implementation of Generalized Tree.

6.2. Generative Binary Recursion

When we are discussing the problem subdivided into sub problems, then Binary Search would be a simplest and natural example. In Binary search we break a sorted vector into two for each comparison. Here is the simple implementation of Binary Search.

This function is similar to the AddItem function of the BinaryTree. Although there are two recursive calls of BinarySearch, for any given value there will be only one call that can occur. This program is not a truly Binary Recursion, because the recursive function doesn’t call itself more than once during any call.

Let’s take a look at the example of merge sort. In merge sort we subdivide our array (or vector) in two pieces and keep doing this until there is only one element left in the sub array, and then we merge all the sub arrays together. The splitting piece of this program is quite straightforward and the real fun part is in the mergeVectors function. We can implement mergeVectors function in two ways. Either we can create a new temporary vector and insert elements in it or we simply swap the values of existing vectors. In this program we use the second approach i.e. swap the values of existing vector rather than create a new one.

// Function to merge two lists// In this function we are just swaping the values of two vectors// using nested loop, without creating any new temporary vectorvoid mergeVectors(std::vector<int>::iterator iStart,
std::vector<int>::iterator iMid,
std::vector<int>::iterator iEnd)
{
// Traverse through the first listfor (std::vector<int>::iterator iter_ = iStart;
iter_ != iMid;
++iter_)
{
// if Current element of First list // is greater than first element of second list// We do not check elements in the first list// because both list should already sortedif (*iter_ > *iMid)
{
// Store the current value in temporary variableint iTemp = *iter_;
*iter_ = *iMid;
// Store the first position in temporary locaiton
std::vector<int>::iterator iTempIter = iMid;
// Move the all the elements of the list to left// whih are greater than current value// to make room for currnet value to insert at the // first place of second listwhile (iTempIter + 1 != iEnd && *(iTempIter + 1) < iTemp)
{
std::swap(*iTempIter, *(iTempIter + 1));
++iTempIter;
}
// Now we have moved all the elements to the next place// therefore we can copy the current element
*iTempIter = iTemp;
}
}
}
// Recursive function call itself recursivelyvoid mergeSort(std::vector<int>::iterator iStart, std::vector<int>::iterator iEnd)
{
size_t iSize = iEnd - iStart;
if (iSize <= 1)
return;
std::vector<int>::iterator iMid = iStart + (iSize / 2);
// Call itself twice example of Binary Recursion
mergeSort(iStart, iMid);
mergeSort(iMid, iEnd);
mergeVectors(iStart, iMid, iEnd);
}

7. Nested Recursion

This is a special type of recursion when the recursive call is nested. All of the above recursion we can replace with either simple looping or a loop with stack, but this type of recursion cannot be easily replaced by a simple loop. In this type of recursion every recursive function calls another function that is also a recursive function. The nested recursive function can be the function itself or it can be another function altogether.

7.1. Structure Nested Recursion

Nested Link List can be a good example of Structured Nested Recursion. In a simple link list every node of the Link List simply contains data as well as address of next node. In case of nested link list every node of a link list contains two addresses. The first address is just like simple link list contains the address of next node in the link list, but the second node is an address of one more link list.

In other words, every node can contain the address of one more link list. Now the question is what might be the advantage of such a complex data structure, when we can do the same thing with two dimensional arrays? The main problem with two dimensional arrays is that every dimension should have the same length just like a Matrix. However, we can’t make a 2D array with every row containing different number of elements.

The solution of this problem is an Array of pointer, where every element of the array stores the address of another single dimensional array. It will solve our 2D array with variable number of elements in the row as shown in the figure.

</img />

But we still have two problems in it. First because it is an array it is a fixed length and if we want to increase the size of any array then I have to reallocate it in memory, copy all the elements from previous location to new location and de allocate previous memory location, which is of course a very expensive solution both in terms of time as well as memory.

Link Lists came into the picture to solve exactly the same problem. If we store that information in a Link List instead of an array then we can easily grow it dynamically without performing all the expensive steps.

Here we have two different types of Nodes. Node is the same as we studied earlier in the case of Simple Link List. NestedNode has two pointers, one to store the address of the next node of the same type to create a link list of NestedNode type and the other one is to store the header address of the nested link list.

</img />

Here is the simple implementation of both types of nodes in nested link list.

7.2. Generative Nested Recursion

In the previous article we studied the Ackermann function as an example of a nested recursion. Here, instead of repeating the same example we are going to study one more nested recursive function named the McCarthy function also known as McCarthy 91 function.