Chapter 7 The List ADT
Document Sample


Chapter 7: The List ADT
• Chapter 7
– Lists
• Overview
– The List ADT and its uses; dynamic
memory allocation; programming with
linked lists.
Objectives
• 1. Understanding and applying the List ADT.
• 2. Implementing a List Class using an array.
• 3. Implementing a List Class using a linked list.
• 4. Using dynamic allocation and pointers in C++.
• 5. Variations on the linked list.
• 6. Creating a class with overloaded operators.
The List ADT
• Characteristics:
• A List L stores items of some type, called
ListElementType.
• Operations:
• void L.insert(ListElementType elem)
• Precondition: None.
• Postcondition:Lpost = Lpre with an instance
of elem added to Lpost.
List ADT, first
• bool L.first(ListElementType &elem)
• Precondition: None
• Postcondition:If the list is empty, none.
Otherwise, the variable elem contains the
first item in L; the “next” item to be
returned is the second in L.
• Return: true if and only if there is at least
one element in L.
List ADT, next
• bool L.next(ListElementType &elem)
• Precondition: The “first” operation has
been called at least once.
• Postcondition:Variable elem contains the
next item in L, if there is one, and the next
counter advances by one; if there is no
next element, none.
• Return: true if and only if there is a next
item.
A useful exercise
• Define some additional operations
that might be useful for a List ADT.
List traversal
• The process of accessing each item
in the list
• Can be defined in terms of two other
operations
– Accessing the first element in a list
– Accessing the next element in a list
Implementing lists
• A header file for the list ADT
– cx7-1.h (on author’s web page)
– See next slide
• Must include List ADT
– characteristics
– operations
Code Example 7-1
• // Code Example 7-1: List ADT header file
• #include "dslib.h"
• // the type of the individual elements in list is defined here
• typedef int ListElementType;
• // implementation specific stuff here
• class List {
• public:
• List();
• void insert(const ListElementType & elem);
• bool first(ListElementType & elem);
• bool next(ListElementType & elem);
• private:
• // implementation specific stuff here
• };
List();
• Is the list copy constructor
• With no parameters or body it is a
‘default constructor’
void insert
(const ListElementType & elem);
• & means pass by reference
– Value parameters should only be used
for simple types (int, char, etc.) which
have simple copy constructors.
– For more complex data types, avoid the
copy constructor by passing it by
address
• const means the element cannot be
modified in this function
Lists using arrays
• The simplest method to implement a
List ADT is to use an array
• “linear list”, “contiguous list”
• Characteristics are
– Array for storing entries (listArray)
– numberOfElements
– currentPosition
Header file for array list
• // cx7-2.h
• #include "dslib.h"
• // the type of the individual elements in the list is
defined here
• typedef int ListElementType;
• // the maximum size for lists is defined here
• const int maxListSize = 1000;
Code Example 7-2
• class List {
• public:
• List();
• void insert(const ListElementType & elem);
• bool first(ListElementType & elem);
• bool next(ListElementType & elem);
• private:
• ListElementType listArray[maxListSize];
• int numberOfElements;
• int currentPosition;
• };
Array List Constructor
• // cx7-3.cpp
• #include "cx7-2.h"
• List::List()
• {
• // initialize to an empty list
• numberOfElements = 0;
• currentPosition = -1;
• }
Insertion into linear list
• void List::insert(const ListElementType & elem)
• {
• assert(numberOfElements < maxListSize);
• listArray[numberOfElements] = elem;
• numberOfElements++; }
Iterator function: first
• bool List::first(ListElementType & elem)
• {
• if (numberOfElements == 0)
• return false;
• else {
• currentPosition = 0;
• elem = listArray[currentPosition];
• return true;
• }
• }
Iterator function: next
• bool List::next(ListElementType & elem)
• {
• // currentPosition should always be
• // greater than or equal to zero
• assert(currentPosition >= 0);
• if (currentPosition >= numberOfElements - 1)
• return false;
• else {
• currentPosition++;
• elem = listArray[currentPosition];
• return true; }
• }
Simple List Client
• // cx7-4.cpp
• #include "cx7-2.h" // header for Linear List;
ListElementType is int
• int main()
• { List l;
• ListElementType i; // header defines this as int
• cout << "Enter items to add to list, or 0 to stop: ";
• cin >> i;
• while (i != 0) {
• l.insert(i);
• cin >> i; }
Client main continued
• cout << "Here are the items in the list.\n";
• ListElementType elem;
• bool notEmpty(l.first(elem));
• while (notEmpty) {
• cout << elem << endl;
• notEmpty = l.next(elem);
• }
• return 0;
• }
Problems with arrays
• Array implementations of lists use a static data
structure. Often defined at compile-time. Cannot
be altered while program is running.
• This means we usually waste space rather than
have program run out.
• It also means that data must be added to the end.
If inserted in front, others must shuffle down.
This is slow and inefficient.
Figure 7-1
insert 1 here
2 4 5 8 11 13 6
1 2 4 5 8 11 13 6
0 1 2 3 4 5 6 7 8 9 10 11 12 13
indices
Linked list implementation
• Data storage must now contain both
item and pointer to next item.
• These are called ‘nodes’
• This can be made dynamic
• Much more efficient for insertion and
deletion
Figure 7-2
7 12 5
Figure 7-3
head
7 12 5
Adding a node (insertion)
• A four step process
• Add at front of list
– Create new node
– Copy data into it
– Copy head into its link field
– Copy node pointer to head
Figure 7-4
head
7 12 5
9
Figure 7-5
7 12 5
9
head
Adding to end of list
• A five step process
– Create new node
– Copy data into it
– Assign new node ptr to tail->link
– Assign 0 to new node link (not NULL)
– Assign new node ptr to tail
Figure 7-6
tail
head
7 12 5
9
Figure 7-7
head
7 12 5
9
tail
Algorithm 7-1:
List Traversal
• Comment: Assume that “head” is the
name of the external link to the list.
• current = head;
• while current is not NULL {
• process the node current points to;
• current = the link field of the node
current points to;
• }
Linked list example
• typedef int ListElementType;
• class List {
• // Use L to mean "this List"
• public:
• List();
• // Precondition: None
• // Postcondition: L is an empty List
• void insert(const ListElementType & elem);
• // Precondition: None
• // Postcondition: Lpost = Lpre with an
• // instance of elem added to Lpost
First()
• bool first(ListElementType & elem);
• // Precondition: None
• // Postcondition: If the list is empty, none.
• // Otherwise, the variable
• // elem contains the first item in L;
• // the "next" item returned is the second in L.
• // Returns: true, if and only if,
• // there is at least one element in L.
List class, public (con’t)
• bool next(ListElementType & elem);
• // Precondition: The "first" operation has been
called at least once.
• // Postcondition: Variable elem contains the next
item in L, if there is one, and the next counter
advances by one; if there is no next element,
none.
• // Returns: true if and only if there was a next
item.
7.5 (con’t) private of next
• private:
• struct Node; // declaration without definition
• typedef Node *Link; // use declaration of Node
• struct Node { // now we define Node
• ListElementType elem;
• Link next;
• };
• Link head;
• Link tail;
• Link current;
• };
Linked list constructor
• List::List()
• {
• // Initialize an empty list
• head = 0;
• tail = 0;
• current = 0;
• }
Insert for linked list
• void List::insert(const ListElementType & elem)
• {
• Link addedNode(new Node);
• assert(addedNode); // check whether node was allocated
• addedNode->elem = elem;
• if (head == 0) // list was empty -- set head
• head = addedNode;
• else
• tail->next = addedNode;
• tail = addedNode;
• addedNode->next = 0;
• }
An easy mistake to make
• //unsafe test of pointer with 0
• int main() {
• int * p; //p is a pointer to an int, initialized to 0
• if (p = 0) //obviously, == was intended
• cout << “zero pointer\n”;
• else
• cout << “non-zero pointer\n”;
• return 0;
• }
Dynamic memory allocation
• Use the ‘new’ operator instead of
old C calloc, malloc and realloc
• This draws from the “free store”
• Dynamic allocation occurs at run-
time, not compile time
• To check on availability of memory
use an assertion.
– link newNode = new Node;
– assert(newNode);
Linked list implementation
• List::List()
• {
• // Initialize an empty List
• head = 0;
• tail = 0;
• current = 0;
• }
Code Example 7-8
• void List::insert(const ListElementType & elem)
• {
• Link addedNode = new Node;
• assert(addedNode); // check whether node was allocated
• addedNode->elem = elem;
• if (head == 0) // list was empty -- set head
• head = addedNode;
• else
• tail->next = addedNode;
• tail = addedNode;
• addedNode->next = 0;
• }
First() method
• bool List::first(ListElementType & elem)
• {
• // After calling first, current points to
// first item in list
• if (head == 0)
• return false;
• else {
• elem = head->elem;
• current = head;
• return true;
• }
• }
Next() method
• bool List::next(ListElementType & elem)
• {
• // current should always be nonzero
• assert(current);
• // After each call, current points to the item
• // that next has just returned.
• if (current->next == 0)
• return false;
• else {
• current = current->next;
• elem = current->elem;
• return true;
• }
• }
Figure 7-8
head current
7 12 5
elem next
head current
7 12 5
elem next
The Inorder List ADT
• Many applications require that lists
be maintained in some order
– Address books
– File names
– County records
– Student records
– Dictionary
Inorder List requirements
• Some part of the information stored
must be a designated key
• For any two keys (k1, k2) there must
be a way to evaluate them, such as
k1 < k2
• A ‘total order’ is any set of keys that
obey an ordering rule.
The Inorder List ADT
• Characteristics:
• An Inorder List L stores items of some
type (ListElementType) that is totally
ordered.
• The items in the List are in order; that is,
if a and b are elements of ListElement
Type, and a < b, then if a and b are in L,
a will be before b.
Inorder List operations
• Prerequisite:
• ListElementType must work with the
operations <= and ==.
• Operations:
• void L.insert(const ListElementType &elem)
• Precondition: None.
• Postcondition:L = L with an instance of
elem added to the list
First() method
• bool L.first(ListElementType &elem)
• Precondition: None
• Postcondition: If the list is empty, none.
Otherwise, the variable elem contains the
smallest item in L; the “next” item to be
returned is the second in L.
• Return: true if and only if there is at least
one element in L.
Next() method
• bool L.next(ListElementType &elem)
• Precondition: The “first” operation has
been called at least once.
• Postcondition:Variable elem contains the
next item in L, in order, if there is one.
• Return: true if and only if there is a next
item.
Inorder invariant
u v
invariant: u < v
Insertion (before)
7 12
9
Insertion (after)
7 12
9
Required insertion pointers
7 12
pred 9
addedNode
Assertions
(before insertion)
pred->elem pred->next pred->next->elem
7 12
pred 9
addedNode
addedNode->elem
Assertion: pred->elem <= addedNode->elem &&
addedNode->elem <= pred->next->elem
Assertion
(considering end-of-list)
• (pred->elem <= addedNode->elem)
&&
• (addedNode->elem <=
pred->next->elem
|| pred->next == 0).
Assertion
(after insertion)
pred->elem pred->next pred->next->next->elem
7 12
pred 9
pred->next->elem pred->next->next
Assertion: pred->elem <= pred->next->elem
<= pred->next->next->elem
Assertions
for continued advancing
• 7-2 addedNode->elem >
pred->next->elem
• 7-3 pred->next != 0
Insert for Inorder List ADT
• // cx7-9.cpp
• // cx7-8.cpp
• // implementation file, linked list implementation
of List ADT
• #include "cx7-5.h"
• void List::insert(const ListElementType & elem)
• {
• // precondition: list is in order
• Link addedNode(new Node);
Code Example 7-9
• assert(addedNode);
• addedNode->elem = elem;
• // Special case: if existing list is empty, or if the new data
• // is less than smallest item in the list, new node is added
• // to the front of the list
• if (head == 0 || elem <= head->elem) {
• addedNode->next = head;
• head = addedNode;
• }
• else {
• // find the pointer to the node that is the predecessor
• // to the new node in the in-order list
Code Example 7-9
• Link pred(head);
• // assertion: pred->elem <= addedNode->elem
• while
(pred->next != 0 && pred->next->elem <= addedNode-
>elem)
• // invariant: pred->next != 0 && pred->next->elem <= elem
• pred = pred->next;
• // assertion 7-1: (pred->elem <= addedNode->elem) &&
• //(addedNode->elem <= pred->next->elem || pred->next == 0)
• addedNode->next = pred->next;
• pred->next = addedNode;
• // assertion: pred->elem <= pred->next->elem &&
• // (pred->next->elem <= pred->next->next->elem ||
pred->next->next == 0)
• // postcondition: list is in order, with elem added
Variations on linked lists
• Dummy head nodes
– Eliminates special case surrounding
first node in list
– Never insert or delete a first node
• Circular linked lists
• Doubly linked lists
Empty linked lists
(head == 0)
(dummy)
List comparisons
7 4
(dummy) 7 4
List class
for list with dummy node
• class List {
• // Use L to mean "this List"
• public:
• List();
• // Precondition: None
• // Postcondition: L is an empty List
• void insert(const ListElementType & elem);
• // Precondition: None
• // Postcondition: Lpost = Lpre with an instance of
elem added to Lpost
List operations (first)
• bool first(ListElementType & elem);
• // Precondition: None
• // Postcondition: If the list empty, none.
Otherwise, variable elem contains first item
in L; the "next" item to be returned is the
second in L.
• // Returns: true if and only if there is at least one
element in L
List operations
(next, remove)
• bool next(ListElementType & elem);
• // Precondition: The "first" operation has been
called at least once.
• // Postcondition: elem contains next item in L, if
there is one, and next counter advances by
one; if no next element,none.
• // Returns: true if and only if there was a next item.
• void remove(const ListElementType & target);
• // Precondition: None
• // Postcondition: Lpost = Lpre with one instance of
target removed
Data members
• private:
• struct Node; // declaration without definition
• typedef Node *Link; // use declaration of Node
• struct Node { // now we define Node
• ListElementType elem;
• Link next;
• };
• Link head;
• Link current;
• };
Modifications (constructor)
• // cx7-11.cpp
• #include "cx7-10.h"
• List::List()
• {
• // Initialize an empty list
• head = new Node;
• assert(head); // What is the reason for this?
• head->next = 0;
• current = 0;
• }
Modification to insert()
• void List::insert(const ListElementType & elem)
• { // precondition: list is in order
• Link addedNode(new Node);
• assert(addedNode);
• addedNode->elem = elem;
• // find pointer to predecessor in the in-order list
• Link pred(head);
• // loop invariant: pred>elem <= elem
• while (pred->next != 0 && (pred->next->elem <=
addedNode->elem))
• pred = pred->next;
Insertion (con’t)
• // assertion: (pred>elem <= addedNode>elem) &&
• //(addedNode->elem <= pred->next->elem
• // || pred->next == 0)
• addedNode->next = pred->next;
• pred->next = addedNode;
• // postcondition: list is in order
• }
Modification of first
• bool List::first(ListElementType &elem)
• { // After first(), current points to first item in list
• assert(head); // if no head, something is wrong!
• if (head->next == 0)
• return false;
• else {
• current = head->next;
• elem = current->elem;
• return true;
• }
• }
Modification of next()
• bool List::next(ListElementType & elem)
• { // With proper use, current should be nonzero
• assert(current);
• // After each call, current points to item returned.
• if (current->next == 0)
• return false; // no next element available
• else {
• current = current->next;
• elem = current->elem;
• return true;
• }
• }
Modification of remove()
• void List::remove(const ListElementType & target)
• { assert(head);
• Link pred, delNode;
• // pred starts out pointing at the dummy head
• for (pred = head; pred->next != 0 && pred->next->elem <
target;pred = pred->next);
• // at this point, check to see if we've found target --
• // if so, remove it. Check to avoid dereferencing null pointe
• if (pred && (pred->next) && (pred->next->elem == target))
{ // remove the next node in the list
• delNode = pred->next;
• pred->next = delNode->next;
• delete delNode; // return node to memory}
• }
List before removal
7 12
pred
9
delNode
List after removal
7 12
pred
9
delNode
Circular linked lists
• No ‘0’ pointer at end
• Last link points to first node
• If external pointer is assigned to tail
of list, it is easy to reference both the
tail and the head
Circular linked list
7 12 5
Doubly linked lists
• Allow traversal in either direction
• Require two links for each node
– Next
– Predecessor
A doubly linked list
head
7 12 5
Header file for doubly linked list
• typedef int ListElementType;
• class List {
• public:
• List();
• void insert(const ListElementType & elem);
• bool first(ListElementType & elem);
• bool next(ListElementType & elem);
• bool previous(ListElementType & elem);
Data members
• private:
• struct Node; // declaration without definition
• typedef Node *Link;
• struct Node {
• ListElementType elem;
• Link next;
• Link prev;
• };
• Link head;
• Link current;
• };
Insertion into DLL (front)
• void List::insert(const ListElementType & elem)
• { Link addedNode = new Node;
• assert(addedNode);
• addedNode->elem = elem;
• addedNode->next = head;
• if (head) // test to see if a node exists
• head->prev = addedNode; // if so, it needs to
point back to the new node
• addedNode->prev = 0;
• head = addedNode;
• }
Previous for DLL
• bool List::previous(ListElementType &elem)
• {
• assert(current);
• if (current->prev == 0)
• return false;
• else {
• current = current->prev;
• elem = current->elem;
• return true;
• }
• }
Dynamic Linear Lists
• Arrays (conventional linear lists) are
dimensioned in the program code
and space allocated at compile time.
• Dynamic arrays (dynamic linear lists)
have space allocated for them at run-
time.
• This makes them more versatile than
static linear lists and easier to code
than linked lists.
Dynamic linear list class
• typedef int ListElementType;
• class List {
• public:
• List(int lSize);
• void insert(const ListElementType & elem);
• bool first(ListElementType & elem);
• bool next(ListElementType & elem);
• int size();
• private:
• ListElementType * listArray;
• int numberOfElements;
• int currentPosition;
• int listSize;
• };
Constructor
and size accessor
• List::List(int lSize)
• {
• assert(lSize > 0);
• listSize = lSize;
• listArray = new ListElementType[listSize];
• assert(listArray); // memory was successfully allocated
• numberOfElements = 0;
• currentPosition = -1;
• }
• List::size()
• {
• return listSize;
• }
Dynamic list client
• int main()
• {
• int list1size, list2size;
• cout << "Enter size of the first list: ";
• cin >> list1size;
• List list1(list1size);
• cout << "Enter size of the second list: ";
• cin >> list2size;
• List list2(list2size);
• // . . . and so on . . .
• }
The use of const
• If a const could possibly be passed
to a function, then the function must
be able to accept it.
• Const is generally the proper way to
implement accessors
• It avoid client code blowing up when
it sends a const actual argument into
a function with non-const formal
arguments
Example of use of const
• #include <string>
• class ClubMember {
• public:
• ClubMember();
• void setName(const string & fn, const string & ln);
• void setAddress(const string & ad1, const string & ad2);
• void setTelnum(const string & tn);
• void setGradYear(const int gy);
• void setClubMemberData(const string & fn, const string &
ln, const string & ad1, const string & ad2, const string &
tn, const int gy);
• string getFirstName() const;
• string getLastName() const;
• string getAddrOne() const;
• string getAddrTwo() const;
• string getTelnum() const;
• int getGradYear() const;
Private section of class
• private:
• string firstName;
• string lastName;
• string addrOne;
• string addrTwo;
• string telNum;
• int gradYear;
• };
Const problem situation
• Void printMember(const ClubMember &member)
• {
• cout << member.GetFirstName()
• If GetFirstName is not const the compiler will
assume it may change any involved parameters
and disallow this for a printMember where the
parameter was const.
• string getFirstName() const; // is OK
• string getFirstName(); // is dangerous
Operator overloading
• This is important when using class
objects (derived types)
• Standard operators are designed
only to work with native types.
• If you invent a class and intend to
use operators, you must overload
them for your class objects.
<= operator overloading
• // We'll use the names lhs -- short for left hand side -- as the
• // name for the argument on the left of an operator,
• // and rhs -- right hand side -- for the argument on the right.
• int operator<=
(const ClubMember & lhs, const ClubMember & rhs)
• {
• if (lhs.getLastName() == rhs.getLastName())
• return lhs.getFirstName() <= rhs.getFirstName();
• else
• return lhs.getLastName() <= rhs.getLastName();
• }
Chapter Summary
• A List ADT represents items that can be retrieved in
some order.
• Linear lists implement the List ADT using an array.
• Iterator functions can be used to retrieve items in a
List.
• Linked list provide greater flexibility by breaking the
connection between the logical idea of a list and its
implementations.
• Dynamic memory allocation allows a program to
allocate memory at runtime.
Chapter Summary
• The Inorder List ADT maintains items in a
specified order.
• Dummy head-nodes, circular linked lists, and
doubly-linked lists provide alternative
approaches to the implementation of a linked list.
• Client applications may need to implement
particular functions for classes stored within
another class.
• Operator overloading provides a way to make
built-in operators meaningful for user-defined
classes.
Get documents about "