Reading: Dietel section 15.4
Sequence ADT
Implementation as list template class
- Ordered collection of homogenous elements
- Can be constructed and destroyed
- Can insert and remove elements (at a particular element)
- Can be cleared (emptied)
- Can be traversed (front to back)
- Has a number of elements (which can be accessed)
- Can be copied and assigned
List Class
- Doubly linked list -- each element has pointer to previous and next
- Constant-time insert: need only change four pointers
inserting C between A and B
A's next points to C
B's previous points to C
C's previous points to A
C's next points to B
assumes C's location is known- Constant-time remove: need only change two pointers
removing B between A and C
A's next points to C
C's previous points to A
assumes B's location is known- No random-access (no [ ], etc.)
would need to traverse entire list to get to particular position- Uses iterators to implement traversal
begin( ) points to first element
end( ) points to one-past-last element
can use like pointers- Also front( ) and back( ) to return first and last element
template < class T > class ListNode
{
template < class TT > friend class List ; // List can access ListNode's private data / methods
// Can't have a List without ListNodes
public:
ListNode ( const T& ) ; // constructor; needs T object
private:
T data; // the stored object
ListNode < T > * next_ptr; // the next object
ListNode < T > * prev_ptr; // the previous object
} ;
template < class T>
ListNode < T > :: ListNode ( const T& t )
{
data = t;
next_ptr = prev_ptr = 0;
// Null pointers; List class will set these appropriately
}
template < class T >
class List
{
public:
List ( ) ; // constructor
~List ( ) ; // destructor
List ( const List < T > & ); // copy constructor
List < T > & operator= ( const List < T > &);
bool Contains ( const T& ) const; // Is this object in the list?
bool IsEmpty ( ) const;
int NumElements ( ) const;
void Print ( ostream& = cout ) const; // we'll also write a << operator
void InsertAfter ( iterator position, const T& new_value );
void InsertBefore ( iterator position, const
T& new_value );
void InsertAtFront ( const T& new_value );
void InsertAtBack ( const T& new_value );
void Delete ( iterator position ); // Deletes item pointed to by iterator
T front ( ); // Value of first element
T back ( ); // Value of last element
class iterator
{
public:
iterator ( );
T& operator* ( ) const;
iterator& operator ++ ( ); // Prefix operator
iterator& operator -- ( ); // Prefix operator
bool operator == ( const iterator& ) const;
bool operator != ( const iterator& ) const;
friend class List;
private:
ListNode < T > * current;
iterator ( ListNode < T > * ); // Private constructor; use in begin
function
} ;
iterator begin ( ); // Points to first element
iterator end ( ); // Points to one-past-last element
// begin, end functions are not const; they will allow changes to be made to the List
private:
ListNode < T > * head; // first element
ListNode < T > * tail; // one past last
valid element
int num_elements;
void Copy ( const List < T > & ) ; // To be used in copy constructor, assignment operator
void Destroy ( ) ; // To be used in destructor, assignment operator
} ;
template < class T >
List < T > :: iterator :: iterator
( ) // Left-to-right :: associativity
{
current = 0;
}
template < class T >
T& List < T> :: iterator :: operator* ( )
{
if ( current -> next_ptr ) //
Don't access dummy node's data
return current -> data; // Throw an exception if no current element
}
template < class T >
iterator& List < T > :: iterator :: operator++
( )
{
if ( current -> next_ptr == 0 )
// Throw some exception
current = current -> next_ptr;
return *this;
}
template < class T >
iterator& List < T > :: iterator :: operator
-- ( )
{
if ( current -> prev_ptr == 0 )
// Throw some exception
current = current -> prev_ptr;
return *this;
}
template < class T >
bool List < T > :: iterator :: operator
== ( const List < T > :: iterator& i ) const
{
return ( current == i . current );
}
template < class T >
bool List < T> :: iterator :: operator
!= ( const List < T > :: iterator& i
) const
{
return ! ( *this == i );
}
template < class T >
List < T > :: iterator :: iterator ( ListNode < T > * nodeptr) // private constructor
{ current = nodeptr; }
template < class T >
List < T > :: List ( )
{
num_elements = 0;
head = tail = new ListNode < T > ( T
( ) ); //
Dummy node; represents "one past end"
}
template < class T >
List < T > :: Destroy ( ) // Deallocate all list elements
{
ListNode < T > * next;
ListNode < T > * curr = head;
while ( curr ) // not null pointer
{
next = curr -> next_ptr;
delete curr;
curr = next;
}
}
template < class T >
List < T > :: ~List ( ) // Destructor
{
Destroy ( );
}
template < class T >
List < T > :: iterator List < T > :: begin
( )
{
return iterator ( head ); // Use of private iterator constructor
}
template < class T >
List < T > :: iterator List < T > :: end
( )
{
return iterator ( tail ); // Points to dummy node
}
template < class T >
void List < T > :: Copy ( const List < T >& rhs )
{
num_elements = rhs.num_elements;
head = tail = new ListNode < T > ( T ( ) ); // Assumes T default constructible
iterator i = rhs.begin ( );
ListNode < T > * curr;
ListNode < T > * next;
if ( i != rhs.end ( ) )
{
head = new ListNode < T > ( * i ); // ListNode's copy constructor
head -> next_ptr = tail; // no next element besides dummy (yet)
tail -> prev_ptr = head;
++ i; // move to next element
curr = newlist -> head;
}
while ( i != rhs.end ( ) ) // while there are still more elements to be copied
{
curr -> next_ptr = new ListNode < T > ( *i ); // copy the next element from rhs
( curr -> next_ptr ) -> prev_ptr = curr;
curr = curr -> next_ptr;
++ i;
}
curr -> next_ptr = tail; // handle last node separately; points to dummy tail node
tail -> prev_ptr = curr;
}
template < class T >
List < T > :: List ( const List < T > & rhs ) // copy constructor
{
Copy ( rhs );
}
template < class T >
List < T >& List < T > :: operator= ( const List < T > & rhs )
{
if ( this != &rhs )
{
Destroy ( ); // Deallocate all current list elements
Copy ( rhs );
}
return *this;
}
template < class T >
bool List < T > :: Contains ( const T& value ) const
{
ListNode < T > * curr = head;
while ( curr -> next_ptr != 0 ) // Don't check the dummy node's value
{
if ( curr -> data == value ) // uses T's equality operator
return true;
curr = curr -> next_ptr;
}
return false;
}
template < class T >
bool List < T > :: IsEmpty ( ) const
{ return ( num_elements == 0 ); }
template < class T >
int List < T > :: NumElements ( ) const
{ return num_elements; }
template < class T >
void List < T > :: Print ( ostream& o ) const
{
ListNode < T > * curr = head ;
while ( curr -> next_ptr != 0 ) // Don't print dummy node's value
{
o << curr -> data << " "; // assumes << operator for T type
curr = curr -> next_ptr;
}
}
template < class T >
ostream& operator << ( ostream& o,
const List < T >& L)
{
List < T > :: iterator
i = L.begin ( );
while ( i != L.end ( ) )
o << *i << endl;
return o;
}
template < class T >
T List < T > :: front ( ) const
{
if ( num_elements )
return head -> data;
// Throw an exception if list empty
}
template < class T >
T List < T > :: back ( ) const
{
if ( num_elements )
return tail -> prev_ptr -> data;
// Throw an exception if list empty
}
template < class T >
void List < T> :: InsertAfter ( iterator position, const T& value )
{ // Assumes iterator points to an element in the lhs list
if ( i == end ( ) )
// Throw some exception; can't insert after the dummy node
ListNode < T > * node_before_new = position . current;
ListNode < T > * node_after_new = node_before_new -> next_ptr;
ListNode < T > * new_node = new ListNode < T > ( value ); // ListNode constructor
node_before_new -> next_ptr = new_node;
new_node -> prev_ptr = node_before_new;
new_node -> next_ptr = node_after_new;
node_after_new -> prev_ptr = new_node;
num_elements++; // one more element now
}
template < class T >
void InsertBefore ( iterator position,
const T& value )
{ // assumes iterator points to an element of the lhs List
if ( position != head ) // Not inserting before the first
element
InsertAfter ( -- position, value );
else
{
ListNode < T > * new_node = new ListNode < T > ( value );
new_node -> next_ptr = head;
head -> prev_ptr = new_node;
head = new_node;
num_elements ++;
}
}
template < class T >
void List < T > :: InsertAtFront ( const T& value )
{
InsertBefore ( begin ( ), value );
}
template < class T >
void List < T > :: InsertAtBack ( const T& value )
{
InsertBefore ( end ( ), value );
}
template < class T >
void List < T > :: Delete ( iterator position )
{ // Assumes iterator points to an element of
the lhs List
if ( position . current == head )
{
head = head -> next_ptr;
head -> prev_ptr = 0;
delete
position . current;
}
else if ( position . current == tail )
// Throw some exception; can't delete dummy node
else
{
ListNode < T > * node_before = ( position . current ) -> prev_ptr;
ListNode < T > * node_after = ( position . current ) -> next_ptr;
node_before
-> next_ptr = node_after;
node_after -> prev_ptr = node_before;
delete
position . current;
}
num_elements --;
}
List Usage Examples
List < int > IntList;
IntList . InsertAtFront ( 8 ); // IntList is now: 8
IntList . InsertAtBack ( 5 ); // IntList is now: 8 5
List < int > :: iterator i = IntList . begin ( );
IntList . InsertAfter ( i , 3); // IntList is now: 8 3 5
for ( ; i != IntList.end( ); ++i )
*i *= 2; // After loop, IntList is: 16 6 10
List < Polynomial > PolyList;
for ( int i = 0; i < 5 ; i++ )
PolyList . InsertAtFront ( Polynomial ( 4, CoefficientList [ i ] ); // CoefficientList is 2-D array of doubles
List < Polynomial > :: iterator p = PolyList . begin ( );
while ( p != PolyList . end ( ) )
{
cout << i << "th polynomial: " << *p << endl; // This would use Polynomial's << operator
++ p;
}
PolyList . Delete ( PolyList.begin ( ) ) ; // Removes first element
PolyList . Delete ( -- ( PolyList . end ( ) ) ) ; // Removes last element