Docstoc

Chapter 7 The List ADT

Document Sample
Chapter 7 The List ADT Powered By Docstoc
					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.

				
DOCUMENT INFO
Shared By:
Categories:
Tags:
Stats:
views:2
posted:4/27/2012
language:
pages:98