Chapter 3 Lists, Stacks, and Queues Abstract Data Types, Vectors • Sections 3.1, 3.2, 3.3, 3.4 • Abstract Data Types (ADT) • Iterators • Implementation of Vector 1 Abstract Data Type (ADT) • High-level definition of data types • An ADT specifies – A collection of data – A set of operations on the data or subsets of the data • ADT does not specify how the operations should be implemented • Examples – list, stack, queue, deque, priority queue, table (map), associative array, set, graph, digraph • How are they different? – Class – Class template – ADT 2 List ADT • Objects/data – A0, A1, A2, … A N-1 – Size of the List is N • Operations – – – – – – – – Up to the designer of a List, for example, printList() makeEmpty() Find() Insert() Remove() findKth() etc 3 Iterators: Motivation • Need a way to navigate through the items in a container. • An example: navigating over vector v. for (int i = 0; i != v.size(); i++ ) cout << v[i] << endl; • However, doubly-linked list would need a different form • We want a general approach to navigate elements for different implementations of an ADT 4 Iterators • A generalized type that helps in navigating a container – A way to initialize at the front and back of a list – A way to move to the next or previous position – A way to detect the end of an iteration – A way to retrieve the current value • Implemented as nested types of containers in STL • Examples: – Iterator type for vector<int> defined as • vector<int>::iterator itr; – Iterator type for list<string> defined as • list<string>::iterator itr; 5 Getting an Iterator • Two methods in all STL containers – iterator begin ( ) • Returns an iterator to the first item in the container – iterator end ( ) • Returns an iterator representing end marker in the container (that is, the position after the last item) • Example for (int i = 0; i != v.size(); i++ ) cout << v[i] << endl; can be written using iterators as for(vector<int>::iterator itr=v.begin(); itr!=v.end(); itr++) cout << itr++ << endl; 6 Iterator Methods • Iterators have methods • Many methods use operator overloading – itr++ and ++itr advance the iterator to next location – *itr return reference to object stored at iterator itr’s location – itr1 == itr2 true if itr1 and itr2 refer to the same location, else false – itr1 != itr2 true if itr1 and itr2 refer to different locations, else false 7 Container Operations Requiring Iterators • Adding element – iterator insert(iterator pos, const object &x) – Add x in list before interator pos – Returns iterator representing position of inserted item • Removing element – iterator erase(iterator pos) – Remove element at position pos – Returns iterator representing position of item following pos • Removing elements in a range – iterator erase(iterator start, iterator end) – Remove elements from start to end (not including end) 8 Iterator example • Removing every other elements in a list 9 The Vector Implementation of List ADT • Extends the notion of array by storing a sequence of arbitrary objects – Informally, we call it Vector ADT • Elements of vector ADT can be accessed by specifying their index 10 vector in C++ STL • Collection Elements of some proper type T • Operations – int size ( ) returns the number of elements in the vector – void clear ( ) removes all elements from the vector – bool empty ( ) returns true if the vector has no elements – void push_back ( const Object &x ) • adds x to the end of the vector – void pop_back ( ) • Removes the object at the end of the vector – Object & back ( ) • Returns the object at the end of the vector – Object & front ( ) • Returns the object at the front of the vector 11 vector in C++ STL (contd.) • More Operations – Object & operator[] ( int index ) • Returns the object at location index (without bounds checking) • Both accessor and mutator versions – int capacity ( ) • Returns the internal capacity of the vector • Number of elements the container can hold without further memory allocation – void resize( int newSize, const Object& val = Object() ) • Change the size of the vector • Newly created elements will be initialized to val 12 Implementing the vector Class • Implementing Vector as first-class type – Can be copied – Memory it uses is automatically reclaimed • Vector maintains – A primitive C++ array – The array capacity – The current number of items stored in the vector • Operations – Copy constructor – operator= – Destructor to reclaim primitive array – All the other operators we saw earlier 13 vector Implementation template <typename T> class vector { public: // lots of member functions typedef T * iterator; private: int theSize; int theCapacity T *Array }; 14 vector Member Functions - 1 vector(int initialSize=1) : theSize(initialSize),theCapacity(initia lSize+1) {Array = new T[theCapacity];} ~vector() {delete [] Array;} void pop_back() {theSize--;} O(1) iterator begin() {return Array;} iterator end() {return Array + theSize;} T &operator[](int index) {return Array[index];} O(1) 15 vector Member Functions - 2 vector &operator=(vector &rhs) { if(this != &rhs) // Prevents self-copy { delete Array; theSize = rhs.size(); theCapacity = rhs.theCapacity; Array = new T[ capacity() ]; for(int k=0; k<size(); k++) Array[k] = rhs.Array[k]; } } 16 vector Member Functions - 3 void reserve(int N) { if(N < theSize) return; Array old T *old = Array; Array = new T[N]; for(int k=0;k<theSize;k++) void push_back(T &x) Array[k] = old[k]; { if(theSize==theCapacity) theCapacity = N; reserve(2*theSize); delete [] old; // Assumes theSize > 0 } Array[theSize++] = x; O(N) } 17 Amortized Analysis of push_back • Consider the time for N push_backs – Time for copying the data = N*O(1) = O(N) – Let 2k <= N < 2k+1 – Time for call to reserve = O(1 + 2 + 4 + ... + 2k+1) = O(2k+2-1) = O(2N) = O(N) – So, the total amortized time per push_back is O(1) 18