Linked Lists

Steven J. Zeil

Last modified: Jul 16, 2014

Contents:
1. Linked Lists: the Basics
2. Coding for Linked Lists
2.1 Traversing a Linked List
2.2 Searching a Linked List
2.3 Adding to a Linked List
2.4 Removing from a Linked List
2.5 Copying and Clean-up
3. Variations: Headers with First and Last
4. Variations: Doubly-Linked Lists
4.1 Doubliy-Linked List Algorithms

1. Linked Lists: the Basics


Sequences: arrays

Consider the abstraction: a sequence of elements


Sequences: Linked Lists

nodelinked

Linked List Nodes

A linked list node:

struct LinkedListNode {
   DataType data;
   LinkedListNode* next;
};
LinkedListNode* firstNode;


Basic List Operations

2. Coding for Linked Lists


The Data Types

We need two data types to build a linked list:


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.1 Traversing a Linked List


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.


Printing a collection - while loop

// 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 << "}";
}


Printing a collection - for loop

// 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.2 Searching a Linked List


Searching a Linked List

To support:

class BidderCollection {
    ⋮
  Position findBidder (std::string name) const;
  // Returns the position where a bidder matching 
  // 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->link;
  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->link;
  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 to walk two lists:

bcequality.cpp

2.3 Adding to a Linked List


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;
}

add functions

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);
     ⋮

Let’s look at how to do addToEnd first.


addToEnd

addToEnd.cpp


addToEnd Example

We create a new node.

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

If the list is not empty, we will have to find a pointer to the last node currently in the list.


  if (first == NULL)
    {
      first = newNode;
    }
  else
    {
      // Move to last node 
      LListNode<Data>* current = first;

 
      // Move to last node 
      LListNode<Data>* current = first;
      while (current->next != NULL)
        current = current->next; 

Eventually we find it.


      while (current->next != NULL)
        current = current->next;

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

Run the Demo

addInOrder


addInOrder

addInOrder.cpp


addInOrder Example

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;
       ⋮

prevcurrent
  ⋮
      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);
    }
}


Now we just add the new data after the prev position.

And that’s one of the functions we need to implement anyway.

addAfter


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;
}


addAfter 2

Create the new node:

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;
}


addAfter 3

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

afterThis

And we’re done!

2.4 Removing from a Linked List


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


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.5 Copying and Clean-up


Copying

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

append.cpp

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;
}


Be careful when deleting

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.


Example: The List-based Collection

auction/biddercollection.h

The above code makes use of some utility functions for singly-linked lists:

auction/sllistUtils0.h

3. Variations: Headers with First and Last


Adding on Either End

Adding to (either) end of a list is very common, but compare 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;
    }
}



Adding a Last Pointer

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

fllist.h


Look at the Change

template <typename Data>
void LListHeader<Data>::addToFront (const Data& value)First-Last Header Algorithms
{
  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;
    }
}

First-Last Header Algorithms

auction/sllistUtils.h

4. Variations: Doubly-Linked Lists


Doubly-Linked Lists


dllist.h

we can


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);
    }
}


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);
    }
}

4.1 Doubliy-Linked List Algorithms

auction/dllistUtils.h