Upload
corin
View
66
Download
6
Tags:
Embed Size (px)
DESCRIPTION
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. - PowerPoint PPT Presentation
Citation preview
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 vinvariant: 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.