Topics To Cover

Recursive structure of a list

It turns out that a lot of linked list operations are very easily accomplished with recursion. The reason for this is that there is an essentially recursive way of looking at lists. Recall that "a list" in our program is really a pointer to the first node in a linked list.

Suppose LIST is a variable of type Node* in our program, and suppose that it points to the list 87, 42, 53, 4 as illustrated in the following picture:

Recursive structure of a list:
A list that LIST represents = (the first data, i.e., LIST->data) + (the list that LIST->next represents)

Printing a list

To write a recursive function to print out a linked list, we can start with the template below, and then fill in the two comment lines.
Base case
First we have to answer the question, "What should happen in the base case?" Another way of putting that is, "What should happen when we try to print an empty list?"

And the answer is, nothing! So we can leave that part blank in the code.

Recursive case
The second question is, "What do we do for each node in the list?"

That's easy too — you print out the node's data field in step 1!


void printlist(Node* L) 
{
  if (L == NULL) {
    // base case:
    //    depends on what you're doing
  } else {
    // recursive case:
    // step 1: Do something with L->data before the recursion

    // step 2: Recursion
    printlist(L->next);

    // step 3: Do something with L->data after the recursion
  }
}


This gives us the complete function:

void printlist(Node* L) {
  if (L == NULL) {
    // printing an empty list - nothing to do here!
  } else {
    cout << L->data << " ";
    printlist(L->next);
  }
}
See what I mean when I said that recursion can be really "natural" with linked lists?

Length()?

You can apply the same idea to get the length of a list. See the practice problems below.

Deleting a list recursively

One place recursion really "shines" with linked lists is in deleting a list. The logic goes like this: This leads to the following simple recursive function to delete a linked list:

void deletelist(Node* L)
{
  // Base case: if the list is empty, there is nothing to delete
  if (L == NULL) 
    return;  

  // Use recursion to delete all the second-to-last nodes
  deletelist(L->next);    
  delete L;               // Then delete the first node
}
Let me again emphasize that the purpose of recursion is not just to have shorter, more "elegant" functions (although that's nice), it's to get comfortable with multiple ways of programming so that you have more options available when solving a new problem.

Sometimes recursion seems "better" for the problem at hand, and sometimes a loop is a more natural solution. But remember that, since you're more used to writing loops now, you won't get a good feel for this until you've written many small recursive functions.

add2back(d, L)

We may want to add a node at the back of a list. For this, we can think of the following function:

Node* add2back(int d, Node* L);
As with add2front() the function returns the pointer to the head (first) node of the updated list.

Recursive add2back()

Adding to the back of list L is actually the same as adding to the back of list L->next Moreover, if L is empty, you are creating a new list containing a node with value d.

Node* add2back(int d, Node* L)
{    
  // base case: 
  //  L is an empty list 
  //  create a list having a single node with d and return it
  if (L == NULL)        
    return new Node{d, NULL};

  // recursive case:
  //  Tell the child list to add a node.
  //  add2back() will give the updated child list (with a new node added back).
  //  L->next points to the child list that add2back() gave. 
  L->next = add2back(d, L->next);


  // Important: 
  //  Recall that a list is represented as a pointer pointing to the head (i.e., first) node. 
  //  In this case, we have no change of the first node, and so return L
  return L;                       
}

About return new Node{d, NULL};

This single line of code is a cool way to create, initialize, and return a node. This means the following:

cool way traditional way

return new Node{d, NULL};

Node* T = new Node;
T->data = d;
T->next = NULL;
return T;

Iterative add2back()

One can implement add2back(d, L) iteratively as follows:

Node* add2back(int d, Node* L)
{
  if (L == NULL)
  { 
    // If the list is empty
    return new Node{d, NULL};
  }
  else
  { 
    // If the list is not empty 
    // Start with finding the last node in the list 
    Node* last = L;
    while(last->next != NULL)
      last = last->next;

    // add2back(): Add a node after the last node
    last->next = new Node{d, NULL};

    // Important: 
    //  Recall that a list is represented as a pointer pointing to the head (i.e., first) node. 
    //  In this case, we have no change of the first node, and so return L
    return L;
  }
}

Mandatory Practice Problems

  1. The length of a list:
    
    int length(Node* );
    

    Solution (drag your mouse):

    int length(Node* L)
    {
      // BASE CASE
      if (L == 0)
        return 0;
    
      // RECURSIVE CASE
      return 1 + length(L->next); 
    }
    
  2. Check if the values in the list is in order.
    
    bool inorder(Node* );
    
    For example, if the list looks like
    10 20 30 40 50
    The function will return true. On the other hand, if the list looks like
    10 20 30 50 40
    The function will return false.

    Solution

Other Practice Problems

  1. Find the maximum value in a list of string's.
    
    string max(Node* );
    

    Solution (drag your mouse):

    string max(Node* L)
    {
      // base case: empty list or 1 node 
      if( L == NULL )
        return "";
      else if (L->next == NULL)
        return L->data;
    
      // recursive case
      string maxC =  max(L->next);  // maximum from the child list
      if( L->data > maxC )
        return L->data;
      else
        return maxC;
    }