Ordered-Access Collections
Queue ADT
- Ordered collection of homogenous elements
- Can be constructed and destroyed
- Can insert and remove elements ( first in is first out )
- Push: insert at back; pop: remove from front
- Can be cleared (emptied)
- Can be copied and assigned
- Has a number of elements (which can be accessed)
Implementation concerns
- Implement as template class
behavior, implementation independent of type of data stored- Implement underneath as an array
can implement as a list, but inefficient- Needs dynamic expansion / contraction
similar to realloc( )- We will double amount of memory when array becomes full
- We will halve amount of memory when array is less than 1/4 full
- Top element is initially in first slot of underlying array;
queue is a contiguous segment of allocated space
Implementation as queue template class
- Array holds data elements
- Push: modify one-past-last element, increment size
- Pop: return first element, decrement size, increment "first" index
- No random-access ( no [ ], no at( ) function )
would allow breaking of ordered insertion/removal- No iterators
also would break access rules
template < class T >
class Queue
{
public:
Queue ( ); // constructs an empty queue
Queue ( const Queue < T > & ) ; // copy constructor
Queue& operator= ( const Queue < T > & ); // assignment
~Queue ( ); // deallocates associated memory
void Clear ( ); // removes all existing elements from queue
void Push ( const T& );
T Pop ( void );
// No "Top" operation; cannot look at top without removing
int NumElements ( void );
bool IsEmpty ( void );
int Capacity ( void ); // User may want to know how long before reallocation
void Print ( ostream& o = cout ); // Doesn't break access rules
// We could Pop, print, Push onto another queue, then undo into original queue
private:
int num_elements; // The number of T objects currently being stored
T* array;
int capacity; // The number of T objects we currently have room for
int first_index; // Location of top element of queue
};
template < class T >
Queue < T > :: Queue ( ) : num_elements ( 0 ) , capacity ( 1 ) // constructor
{
array = new T [ 1 ] ; // Since 2 * 0 is still 0, we want to start with capacity 1
// new [ ] syntax allows us to always use delete [ ] later
first_index = 0; // No elements currently stored, but num_elements is 0 also
}
template < class T >
Queue < T > :: Queue ( const Queue < T > & rhs ) // copy constructor
{
num_elements = rhs . num_elements;
capacity = rhs . capacity;
first_index = 0 ; // Keep elements to the left of the new array
array = new T [ capacity ];
for ( int i = 0; i < num_elements ; i++ )
array [ i ] = rhs . array [ rhs . first_index + i ];
}
template < class T >
Queue < T > & Queue < T > :: operator = ( const Queue < T > & ) // assignment operator
{
if ( this != & rhs )
{
delete [ ] array; // Empty out the left-hand side
num_elements = rhs . num_elements;
capacity = rhs . capacity;
first_index = 0; // Keep elements to left of new array
array = new T [ capacity ];
for ( int i = 0; i < num_elements ; i ++ )
array [ i ] = rhs . array [ rhs . first_index + i ] ; // Won't iterate if num_elements is 0
}
return *this;
}
template < class T >
T Queue < T > :: Pop ( void ) // Remove and return first element
{
if ( num_elements == 0 )
// need to throw some exception
T return_value = array [ first_index ];
num_elements -- ;
first_index ++;
// Now we may need to contract array
if ( num_elements < capacity / 4 )
{
T* new_array = new T [ capacity / 2 ] ;
for ( int i = 0; i < num_elements; i ++ )
new_array [ i ] = array [ first_index + i ]; // Only copy over as many elements as are being stored
delete [ ] array;
array = new_array;
capacity /= 2;
first_index = 0;
}
return return_value;
}
template < class T >
void Queue < T > :: Push ( const T& value )
{
// If we need to use room at the beginning, slide everything over.
if ( first_index + num_elements == capacity && num_elements < capacity )
{
first_index = 0;
for ( int i = 0; i < num_elements; i ++ )
array [ i ] = array [ i + capacity - num_elements ];
}
// Otherwise, if all spaces are occupied, allocate more space.
else if ( first_index + num_elements == capacity )
{
T* new_array = new T [ capacity * 2 ];
for ( int i = 0; i < num_elements; i ++ )
new_array [ i ] = array [ first_index + i ];
delete [ ] array;
array = new_array;
capacity *= 2;
first_index = 0;
}
array [ first_index + num_elements ] = value;
num_elements ++;
}
template < class T >
void Queue < T > :: Clear ( void ) // Empty out the queue
{
delete [ ] array;
num_elements = first_index = 0;
capacity = 1;
array = new T [ 1 ];
}
template < class T >
Queue < T> :: ~Queue ( )
{
delete [ ] array;
}
template < class T >
int Queue < T > :: NumElements ( void )
{
return num_elements;
}
template < class T >
bool Queue < T > :: IsEmpty ( void )
{
return ( num_elements == 0 )
}
template < class T >
int Queue < T > :: Capacity ( void ) // Returns total amount of space allocated
{
return capacity;
}
template < class T >
void Queue < T > :: Print ( ostream& o )
{
o << "Queue has " << num_elements << " elements." << endl;
for ( int i = first_index ; i < first_index + num_elements; i ++ ) // First element first
o << array [ i ] << " "; // Assumes T class implements << operator
o << endl;
}
Queue Usage Examples
Queue < Polynomial > PolyQueue ;
Queue < double > DoubleQueue;
for ( int i = 0; i < 50; i ++ )
DoubleQueue . Push ( i ) ; // Pushes doubles 0 - 49 onto Queue
for ( int i = 0; i < 10; i ++ )
PolyQueue . Push ( Polynomial ( i, SomeArray[ i ] ); // Pushes new Polynomials onto Queue
for ( int i = 0 ; i < 5; i ++ )
DoubleQueue . Pop ( ); // DoubleQueue now holds doubles 5 - 49
PolyQueue . Print ( ) ; // Will use Polynomial's << operator