Linked Lists

Steven J. Zeil

Old Dominion University, Dept. of Computer Science

Table of Contents

1. Linked Lists: the Basics
1.1. Traversing Linked Lists
1.2. Inserting into Linked Lists
1.3. Removing from Linked Lists
2. Coding for Linked Lists
2.1. Utils, not ADTs
2.2. Example:Using the LListHeader
2.3. Traversing a Linked List
2.4. Searching a Linked List
2.5. Adding to a Linked List
2.6. Removing from a Linked List
2.7. Copying and Clean-up
3. Variations: Headers with First and Last
3.1. Adding a Last Pointer
3.2.
4. Variations: Doubly-Linked Lists
4.1. addBefore: Singly Linked
4.2. addBefore: Doubly Linked
4.3.

1. Linked Lists: the Basics

Abstraction: a sequence of elements

  • Arrays (and vectors) work by storing elements of a sequence contiguously in memory

    • Easy to access elements by number

    • Inserting things into the middle is slow and awkward

  • Linked lists store each element in a distinct node. Nodes are linked by pointers.

    • Accessing elements by number is slow and awkward

    • Easy to insert things into the middle

  • A linked list consists of a number of nodes.

  • Each node provides a data field and a next pointer.

1.1. Traversing Linked Lists

We can move from node to node by tracing the pointers:

Oops. This browser does not display Java applets.

1.2. Inserting into Linked Lists

We insert by working from the node prior to the insertion point:

Oops. This browser does not display Java applets.

Quick instructions:

  • You can drag the dividing bars between the sections to adjust the space devoted to text versus graphics.
  • Use the speed control to slow down the animation if desired.
  • Alternatively, use the pause button.
    • When execution pauses inside of an algorithm, press the "step" button to single-step through the code or use the "play" button to resume continual execution.
  • More detailed help is available from the Help menu.

1.3. Removing from Linked Lists

We delete by moving the previous pointer "around" the unwanted node.

Oops. This browser does not display Java applets.

Quick instructions:

  • You can drag the dividing bars between the sections to adjust the space devoted to text versus graphics.
  • Use the speed control to slow down the animation if desired.
  • Alternatively, use the pause button.
    • When execution pauses inside of an algorithm, press the "step" button to single-step through the code or use the "play" button to resume continual execution.
  • More detailed help is available from the Help menu.

2. Coding for Linked Lists

We need two data types to build a linked list:

  • The linked list node

    • I'm doing this as a template so it can be easily re-used

    • The actual kind of data stored is not particularly important

  • The header for the entire linked list

2.1. Utils, not ADTs

  • I am treating these as utilities, not full-fledged ADTs

    • We would use these to build "proper" ADTs like BidCollection

    • So I'm not going to be as picky about encapsulation and providing operators as I would usually be

2.2. Example:Using the LListHeader

class BidderCollection {

  int size;
  LListHeader<Bidder> list;

public:

  typedef LListNode<Bidder>* Position;

  /**
   * Create a collection capable of holding any number of items
   */
  BidderCollection ();
    ⋮

  

In the sections that follow, we will look both at how we would use the linked list (in BidderCollection and BidCollection) and how we would implement it.

2.3. Traversing a Linked List

class BidderCollection {
    ⋮
  // Print the collection
  void print (std::ostream& out) const;

  // Comparison operators
  bool operator== (const BidderCollection&) const;
  bool operator< (const BidderCollection&) const;
};

All of these require us to walk the list, one node at a time, but we've already seen how to do that.

Printing a collection

// Print the collection
void BidderCollection::print (std::ostream& out) const
{
  out << size << "{";
  Position current = list.first; 
  while (current != NULL)
    {
      out << "  ";
      current->data.print (out);
      out << "\n";

      current = current->next;
    }
  out << "}";
}

or

// Print the collection
void BidderCollection::print (std::ostream& out) const
{
  out << size << "{";
  for (Position current = list.first; 
       current != NULL; current = current->next)
    {
      out << "  ";
      current->data.print (out);
      out << "\n";
    }
  out << "}";
}

2.4. Searching a Linked List

To support:

class BidderCollection {
    ⋮
  Position findBidder (std::string name) const;
  // Returns the position where a bidde mathcing the given
  // name can be found, or null if no bidder with that name exists.

we do a traversal but stop early if we find what we are looking for.

findBidder

/**
 * Find the index of the bidder with the given name. If no such bidder exists,
 * return null.
 */
BidderCollection::Position BidderCollection::findBidder 
   (std::string name) const
{
  for (Position current = list.first; current != NULL; 
       current = current->next) 
    {
      if (name == current->data.getName())
        return current;
    }
  return NULL;
}

Searching Linked Lists

As a general utility, we might try to provide:

template <typename Data>
struct LListHeader {

  LListNode<Data>* first;
    ⋮
  // Search for a value. Returns null if not found
  LListNode<Data>* find (const Data& value) const;

  // Search an ordered list for a value. Returns null if not found
  LListNode<Data>* findOrdered (const Data& value) const;

Searching Unordered Linked Lists

  // Search for a value. Returns null if not found
template <typename Data>
LListNode<Data>* LListHeader<Data>::find 
   (const Data& value) const
{
  LListNode<Data>* current = first;
  while (current != NULL && value != current->data)
    current = current->next;
  return current;
}

Searching Ordered Linked Lists

  // Search an ordered list for a value. Returns null if not found
template <typename Data>
LListNode<Data>* LListHeader<Data>::findOrdered 
  (const Data& value) const
{
  LListNode<Data>* current = first;
  while (current != NULL && value > current->data)
    current = current->next;
  if (current != NULL && value == current->data)
    return current;
  else
    return NULL;
}

findBidder (alternate)

/**
 * Find the index of the bidder with the given name. If no such bidder exists,
 * return null.
 */
BidderCollection::Position BidderCollection::findBidder 
   (std::string name) const
{
  Bidder searchFor (name, 0.0);
  return list.find (searchFor);
}

(works only because

bool Bidder::operator== (const Bidder& b) const
{
  return name == b.name;
}


bool Bidder::operator< (const Bidder& b) const
{
  if (name < b.name)
    return true;
  else
    return false;
}

compare only by name).

Walking Two Lists at Once

Although not really a search, the relational operator code is similar, in that we do a traversal with a possible early exit. But now we have two walk two lists:

  // Comparison operators
bool BidderCollection::operator== (const BidderCollection& bc) const
{
  if (size == bc.size)
    {
      Position current = list.first;
      Position bcurrent = bc.list.first;
      while (current != NULL)
        {
          if (!(current->data == bcurrent->data))
            return false;
          current = current->next;
          bcurrent = bcurrent->next;
        }
      return true;
    }
  else
    return false;
}

2.5. Adding to a Linked List

void BidderCollection::add (const Bidder& value)

could be implemented as

void BidderCollection::add (const Bidder& value)
//  Adds this bidder
//Pre: getSize() < getMaxSize()
{
  list.addToEnd (value);
  ++size;
}

or

void BidderCollection::add (const Bidder& value)
//  Adds this bidder
//Pre: getSize() < getMaxSize()
{
  list.addInOrder (value);
  ++size;
}

addToEnd

template <typename Data>
struct LListHeader {

  LListNode<Data>* first;


  LListHeader();

  void addToFront (const Data& value);
  void addToEnd (const Data& value);

  // Add value in sorted order.
  //Pre: all existing values are already ordered
  void addInOrder (const Data& value);
     ⋮

template <typename Data>
void LListHeader<Data>::addToEnd (const Data& value)
{ 
  LListNode<Data>* newNode = new LListNode<Data>(value, NULL);
  if (first == NULL)
    {
      first = newNode;
    }
  else
    {
      // Move to last node 
      LListNode<Data>* current = first;
      while (current->next != NULL)
        current = current->next;
      
      // Link after that node
      current->next = newNode;
    }
}

E.g. Suppose we have a list containing "Adams", "Baker", & "Davis", and we are going to add "Chen".

template <typename Data>
void LListHeader<Data>::addToEnd (const Data& value)
{ 
  LListNode<Data>* newNode = new LListNode<Data>(value, NULL);
 

template <typename Data>
void LListHeader<Data>::addToEnd (const Data& value)
{ 
  LListNode<Data>* newNode = new LListNode<Data>(value, NULL);
  if (first == NULL)
    {
      first = newNode;
    }
  else
    {
      // Move to last node 
      LListNode<Data>* current = first;
 

template <typename Data>
void LListHeader<Data>::addToEnd (const Data& value)
{ 
  LListNode<Data>* newNode = new LListNode<Data>(value, NULL);
  if (first == NULL)
    {
      first = newNode;
    }
  else
    {
      // Move to last node 
      LListNode<Data>* current = first;
      while (current->next != NULL)
        current = current->next;
 

template <typename Data>
void LListHeader<Data>::addToEnd (const Data& value)
{ 
  LListNode<Data>* newNode = new LListNode<Data>(value, NULL);
  if (first == NULL)
    {
      first = newNode;
    }
  else
    {
      // Move to last node 
      LListNode<Data>* current = first;
      while (current->next != NULL)
        current = current->next;

      // Link after that node
      current->next = newNode;
}

addInOrder

template <typename Data>
void LListHeader<Data>::addInOrder (const Data& value)
{
  if (first == NULL)
    first = new LListNode<Data>(value, NULL);
  else 
    {
      LListNode<Data>* current = first;
      LListNode<Data>* prev = NULL;
      while (current != NULL && value > current->data)
        {
          prev = current;
          current = current->next;
        }
      // Add between prev and current
      if (prev == NULL)
        addToFront (value);
      else
        addAfter (prev, value);
    }
}

Again, suppose we have a list containing "Adams", "Baker", & "Davis", and we are going to add "Chen".

template <typename Data>
void LListHeader<Data>::addInOrder (const Data& value)
{
  if (first == NULL)
    first = new LListNode<Data>(value, NULL);
  else 
    {
      LListNode<Data>* current = first;
      LListNode<Data>* prev = NULL;

template <typename Data>
void LListHeader<Data>::addInOrder (const Data& value)
{
  if (first == NULL)
    first = new LListNode<Data>(value, NULL);
  else 
    {
      LListNode<Data>* current = first;
      LListNode<Data>* prev = NULL;
      while (current != NULL && value > current->data)
        {
          prev = current;
          current = current->next;
        }

template <typename Data>
void LListHeader<Data>::addInOrder (const Data& value)
{
  if (first == NULL)
    first = new LListNode<Data>(value, NULL);
  else 
    {
      LListNode<Data>* current = first;
      LListNode<Data>* prev = NULL;
      while (current != NULL && value > current->data)
        {
          prev = current;
          current = current->next;
        }
      // Add between prev and current
      if (prev == NULL)
        addToFront (value);
      else
        addAfter (prev, value);
    }
}

addAfter

template <typename Data>
void LListHeader<Data>::addAfter (LListNode<Data>* afterThis, const Data& value)
{
  LListNode<Data>* newNode = new LListNode<Data>(value, afterThis->next);
  afterThis->next = newNode;
}

template <typename Data>
void LListHeader<Data>::addAfter (LListNode<Data>* afterThis, const Data& value)
{
  LListNode<Data>* newNode = new LListNode<Data>(value, afterThis->next);

template <typename Data>
void LListHeader<Data>::addAfter (LListNode<Data>* afterThis, const Data& value)
{
  LListNode<Data>* newNode = new LListNode<Data>(value, afterThis->next);
  afterThis->next = newNode;
}

2.6. Removing from a Linked List

template <typename Data>
void LListHeader<Data>::removeAfter (LListNode<Data>* afterThis)
{
  LListNode<Data>* toRemove = afterThis->next;
  afterThis->next = toRemove->next;
  delete toRemove;
}
    

remove

template <typename Data>
void LListHeader<Data>::remove (LListNode<Data>* here)
{
  if (here == first)
    {
      LListNode<Data>* after = first->next;
      delete first;
      first = after;
    }
  else
    {
      LListNode<Data>* prev = first;
      while (prev->next != here)
        prev = prev->next;
      prev->next = here->next;
      delete here;
    }
}

Basically removeAfter preceded by a traversal.

2.7. Copying and Clean-up

BidderCollection::BidderCollection (const BidderCollection& bc)
  : size(bc.size)
{
  list.append(bc.list);
}


BidderCollection& BidderCollection::operator= (const BidderCollection& bc)
{
  if (this != &bc)
    {
      list.clear();
      size = bc.size;
      list.append(bc.list);
    }
  return *this;
}

append

// Add all values from another list onto the end of this one
template <typename Data>
void LListHeader<Data>::append (const LListHeader<Data>& list)
{
  // Move to last node 
  LListNode<Data>* last = first;
  while (last->next != NULL)
    last = last->next;

  // Append new nodes onto end of list
  const LListNode<Data>* current = list.first;
  while (current != NULL)
    {
      LListNode<Data>* newNode = new LListNode<Data>(current->data, NULL);
      if (last != NULL)
          last->next = newNode;
      last = newNode;
    }
}

A traversal to the end of the current list, followed by repeated "addToEnd" equivalents.

Collection Destructor

BidderCollection::~BidderCollection ()
{
  list.clear();
}

clear

template <typename Data>
void LListHeader<Data>::clear()
{
  LListNode<Data>* current = first;
  LListNode<Data>* nxt = NULL;
  while (current != NULL)
    {
      nxt = current->next;
      delete current;
      current = nxt;
    }
  first = NULL;
}

Only "trick" here is that we cant do the usual

current = current->next;

at the end of the loop, because we will have already deleted the node that contains next.

3. Variations: Headers with First and Last

Adding to (either) end of a list is very common, but compar the amount of work required:

template <typename Data>
void LListHeader<Data>::addToFront (const Data& value)
{
  LListNode<Data>* newNode = new LListNode<Data>(value, first);
  first = newNode;
}

template <typename Data>
void LListHeader<Data>::addToEnd (const Data& value)
{ 
  LListNode<Data>* newNode = new LListNode<Data>(value, NULL);
  if (first == NULL)
    {
      first = newNode;
    }
  else
    {
      // Move to last node 
      LListNode<Data>* current = first;
      while (current->next != NULL)
        current = current->next;
      
      // Link after that node
      current->next = newNode;
    }
}

3.1. Adding a Last Pointer

We can simplify by adding a second pointer in the header:

template <typename Data>
struct LListHeader {

  LListNode<Data>* first;
  LListNode<Data>* last;

  LListHeader();
    ⋮

template <typename Data>
void LListHeader<Data>::addToFront (const Data& value)
{
  LListNode<Data>* newNode = new LListNode<Data>(value, first);
  first = newNode;
  if (last == NULL)
    last = first;
}

template <typename Data>
void LListHeader<Data>::addToEnd (const Data& value)
{ 
  LListNode<Data>* newNode = new LListNode<Data>(value, NULL);
  if (last == NULL)
    {
      first = last = newNode;
    }
  else
    {
      last->next = newNode;
      last = newNode;
    }
}

4. Variations: Doubly-Linked Lists

By modifying the node structure:

template <typename Data>
struct DListNode
{
  Data data;
  DListNode<Data>* prev;
  DListNode<Data>* next;

  DListNode() {next = prev = 0;}
  DListNode (const Data& d, DListNode<Data>* prv = 0, DListNode<Data>* nxt = 0)
    : data(d), next(nxt), prev(prv)
  {}
};

we can

  • Move backwards as well as forward in the list

    • by following theprev pointers

  • Easily add in front of a node

4.1. addBefore: Singly Linked

template <typename Data>
void LListHeader<Data>::addBefore (LListNode<Data>* beforeThis, 
                                   const Data& value)
{
  if (beforeThis == first)
    addToFront (value);
  else
    {
      // Move to front of beforeThis
      LListNode<Data>* current = first;
      while (current->next != beforeThis)
        current = current->next;
      
      // Link after that node
      addAfter (current, value);
    }
}

4.2. addBefore: Doubly Linked

template <typename Data>
void DListHeader<Data>::addBefore (DListNode<Data>* beforeThis, 
                                   const Data& value)
{
  if (beforeThis == first)
    addToFront (value);
  else
    {
      // Move to front of beforeThis
      DListNode<Data>* current = beforeThis->prev;
      // Link after that node
      addAfter (current, value);
    }
}