Table of Contents
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.
We insert by working from the node prior to the insertion point:
Quick instructions:
We delete by moving the previous pointer "around" the unwanted node.
Quick instructions:
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
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
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.
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.
// 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 << "}";
}
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.
/**
* 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;
}
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;
// 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;
}
// 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;
}
/**
* 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).
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;
}
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;
}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;
}

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


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>::removeAfter (LListNode<Data>* afterThis)
{
LListNode<Data>* toRemove = afterThis->next;
afterThis->next = toRemove->next;
delete toRemove;
}



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

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

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