Chapter 7: The List ADT. Chapter 7 –Lists Overview –The List ADT and its uses; dynamic memory...
-
date post
19-Dec-2015 -
Category
Documents
-
view
245 -
download
2
Transcript of Chapter 7: The List ADT. Chapter 7 –Lists Overview –The List ADT and its uses; dynamic memory...
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
2 4 5 8 11 13 6
2 4 5 8 11 13 61
insert 1 here
0 1 2 3 4 5 6 7 8 9 10111213indices
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
7 12 5
head
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
7 12 5
9
head
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
7 12 5
9
headtail
Figure 7-7
7 12 5
9
head
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
7 12 5
7 12 5
head
head
current
currentelem next
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
9predaddedNode
Assertions (before insertion)
7 12
9predaddedNode
pred->elem pred->next pred->next->elem
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)
7 12
9pred
pred->elem pred->next pred->next->next->elem
pred->next->elem
Assertion: pred->elem <= pred->next->elem <= pred->next->next->elem
pred->next->next
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
(dummy)
7 4
7 4
List classfor 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 pointer!• 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
9pred
delNode
List after removal
7 12
9pred
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
7 12 5
head
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.