Skip to main content

Section 28.9 A Recursive Traversal

Let’s look at a concrete example of a recursive function for linked lists. To do this, we will use a simple singly linked list class that only stores integers. The list itself will just have a head pointer (no size or tail). The code for the class can be found in Listing 28.14.1
The class declares a function int SimpleLinkedList::getTotal(); that returns the sum of all the integers in the list. We will start by examining it. The member function itself is not recursive. It simply calls a helper function, passing it the head pointer of the list.
int SimpleLinkedList::getTotal()
{
    return recursiveGetTotal(head);
}
We already have identified how to test if we are in a base or general case (check for a nullptr). We next need to decide what to do in each case.
In the base case, we are in charge of an empty list. In this case, the sum of the integers is zero. So, if the pointer passed to the helper function is nullptr, we return zero.
In the general case, we are responsible for a single integer. We don’t know what the rest of the list contains. It is somebody else’s problem. However, we do know that if someone tells us the sum of the rest of the list, we can add our integer to that sum to get the total for the entire list we are responsible for:
int recursiveGetTotal(ListNode* current) {
    if(current == nullptr) {
        return 0;
    }
    else {
        int restOfListTotal = recursiveGetTotal(current->next);
        int total = current->data + restOfListTotal;
        return total;
    }
}

Note 28.9.1.

The recursive function is not a member function of the class. We could make a recursive member function, but this implementation helps keep the recursion logic separate from the class interface. Any particular recursive call should only know about the pointer it was given and not the head of the list or any other details that pertain to the entire list.
Listing 28.9.1.
The implemented code has extra debugging output to help understand the recursion process. In it, we can see that we start by asking the 6 node for the total. It then asks the 10 node for the total. The 10 node asks the 3 node for the total. The 3 node asks it’s next (a nullptr) for the total. That if our base case and we return 0. That gets back to the 3 node, which adds its value (3) to 0 and returns 3. The 10 node adds its value (10) to 3 and returns 13. The 6 node adds its value (6) to 13 and returns 19. Finally, the original call to getTotal receives this total and returns it to main.
A visualization of the entire call stack when we finally reach the base case looks like this:
The main stack frame has a linked list with a head pointer. It points at a list with nodes for 6, 10, and 3. Then there are successive stack frames for a recursive function. The first has a current pointer pointing at the 6 node. The next has a current pointer pointing at the 10 node. The next has a current pointer pointing at the 3 node. The final stack frame is holding a null pointer in its current.🔗
Figure 28.9.2. The call stack for a call to getTotal()
The toString function is doing almost the same job as getTotal (only building up a string). However, the implementation shows the alternate base case where we test to see if we are at the last node in the list (its next pointer is nullptr).
//Assumes there is at least one node to work with
string recursiveToString(ListNode* current) {
    if(current->next == nullptr)
        return to_string(current->data);

    string rest = recursiveToString(current->next);
    string result = to_string(current->data) + " " + rest;

    return result;
}
Because of this assumption, the member toString function needs to handle the empty list case itself before calling the helper function:
string SimpleLinkedList::toString() {
    if(head == nullptr)
        return "";
    return recursiveToString(head);
}
Remember that “oops I went too far” is often a simpler base case than looking to see if there really is another node. Our toString function would not have to worry about the empty list case if the recursive function used nullptr as the base case.
However, there are algorithms where “next points to nullptr” is the appropriate base case. Consider the case of getting the largest value in a list. If we use nullptr as the base case, what would the return value be? We wouldn’t want to return 0, as that would produce the wrong largest value for a list like {-2, -5, -1}. Another clue that nullptr would be an awkward based case is that it isn’t clear how we should handle an empty list. What is the largest value in an empty list?
In this case, using “this is the last node” as the base case would be more appropriate. If you are responsible for just the last node, finding the largest value just means returning the data in that node. If you are responsible for some other node, you will return the largest of either the node you are responsible for or the largest value from the rest of the list. The empty list case will be handled outside the recursive function (presumably by throwing an exception or some other indication that there is an error).

Checkpoint 28.9.1.

Build the recursive getLargest function and the non-recursive wrapper function SimpleLinkedList::getLargest().
You have attempted of activities on this page.