11 linear structures

Upload: rama-lakshmi-velusamy

Post on 05-Apr-2018

227 views

Category:

Documents


0 download

TRANSCRIPT

  • 7/31/2019 11 Linear Structures

    1/28

    Chapter 11Linear StructuresIt does not come to me in quite so direct a line as that; ittakes a bend or two, but nothing of consequence.

    Jane Austen, Persuasion, 1818

    Objectives

    To use C++ templates to define polymorphic container classes.

    To recognize that stacks can be implemented using linked lists as well as arrays.

    To learn how to implement queues using both arrays and linked lists as the underlyingrepresentation.

  • 7/31/2019 11 Linear Structures

    2/28

    Linear Structures 384

    The Stack and Queue classes introduced in Chapter 4 are examples of a general categoryof abstract data types called linear structures,in which the elements are arranged in alinear order. This chapter looks at several representations for these ADTs and considershow the choice of representation affects efficiency.

    Because the elements in a linear structure are arranged in an array-like order, using

    arrays to represent them seems like an obvious choice. Indeed, the CharStack classpresented in Chapter 9 is implemented using an array as the underlying representation.Arrays, however, are not the only option. Stacks, queues, and vectors can also beimplemented using a linked list much like the one used to implement the editor buffer inChapter 10. By studying the linked-list implementation of these structures, you willincrease your understanding not only of how linked lists work, but also of how you canapply them in practical programming contexts.

    This chapter has another purpose as well. As noted in Chapter 4, each of these ADTsis a container classes, which means that it can contain data values supplied by the client.To be of maximal utility, a container class should allow the client to store any type ofdata. The template keyword in C++ makes it easy to design a class that can be used formany different types. Such a class is said to be polymorphic.

    11.1 Reimplementing stacks as a template class

    The CharStack class in Chapter 9 defines the relevant operations that all stacks require,but is limited because it can only store elements of type char. This limitation isunfortunate because the behavior of a stack (or any of the other container classes for thatmatter) is not dependent on the type of the values being stored. The client-suppliedvalues are pushed onto the stack, stored internally, and then popped as requested. Theoperations of pushing and popping values are always the same, whether the values beingstored on the stack are strings, doubles, or a user-defined type. The same is true for theenqueue and dequeue operations on a queue.

    To be useful to a range of clients, the stack and other container classes should be

    designed to allow any type of data to be stored. C++ includes a facility for definingtemplates,which are structures that take the base type as a parameter. You have beenusing such templates ever since Chapter 4, but you have not yet seen how to create them.

    A template is basically just a pattern. The pattern describes the interface andimplementation for a class in terms of a placeholder, which is specified in angle bracketsas the template parameter. The placeholder in this case is the particular element typebeing stored. You create a class from the pattern by filling in that placeholder with thedesired element type.

    The interface of a class template

    Changing a non-template class into a template one involves some simple syntacticchanges. For example, if you want to replace the CharStack class from Chapter 9 with a

    general Stack template, you begin by replacing the name CharStack with Stack andthen add the following line before the class definition:

    template

    The template keyword indicates that the entire syntactic unit that follows this lineinthis case the class definitionis part of a template pattern that can be used for manydifferent values of the ElemType parameter. In the class definition that follows, you usethe placeholder name ElemType wherever you need to refer to the type of element being

  • 7/31/2019 11 Linear Structures

    3/28

    Linear Structures 385

    stored. Thus, as you convert the CharStack class definition to a more general templateform, you would replace the appearance of the char type in the prototypes for push,pop, and peek with ElemType.

    You do the same with each of the methods in the implementation file. For example,where the CharStack implementation includes the method

    void CharStack::push(char ch) {if (count == capacity) expandCapacity();elements[count++] = ch;

    }

    you need to replace that with the following template method:

    template void Stack::push(ElemType elem) {

    if (count == capacity) expandCapacity();elements[count++] = elem;

    }

    Note that the body itself doesnt change in any substantive way; the only difference in thebody of the implementation is that the parameter name ch, which is no longer appropriatefor a general stack implementation, has been replaced with a more generic name.

    There is, however, one additional change that you need to make in the class definition.In order for the compiler to create the specific classes that are instances of the generaltemplate, it must have access to both the interface and the implementation. To achievethis goal, you could include the implementation directly in the header file, but this wouldhave the undesirable effect of exposing clients to the details of the implementation. Auseful strategy for providing those details to the compiler while hiding them from theclient is to use the #include facility to read in the implementation when it is needed.

    You can see this technique in Figure 11-1, which specifies the interface for the generic

    Stack class. Both the private section of the class and the actual implementation areincluded from separate files named stackpriv.h and stackimpl.cpp, respectively. Thecontents of these files appear in Figures 11-2 and 11-3.

    11.2 Reimplementing stacks using linked lists

    Although arrays are the most common underlying representation for stacks, it is alsopossible to implement the Stack class using linked lists. If you do so, the conceptualrepresentation for the empty stack is simply the NULL pointer:

    stack NULL

    When you push a new element onto the stack, the element is simply added to the front of

    the linked-list chain. Thus, if you push the element e1 onto an empty stack, that elementis stored in a new cell which becomes the only link in the chain:

    NULL

    stack

    e1

  • 7/31/2019 11 Linear Structures

    4/28

    Linear Structures 386

    Figure 11-1 Interface for the Stack class

    /** File: stack.h* -------------* This interface defines a general stack abstraction that uses* templates so that it can work with any element type.

    */

    #ifndef _stack_h#define _stack_h

    /** Template class: Stack* -------------------------------* This class template models a stack, which is a linear collection* of values stacked one on top of the other. Values are added and* removed only from the top of the stack. The fundamental stack* operations are push (add to top) and pop (remove from top).* Because values are added and removed from the same end of the* stack, the last value pushed on a stack is the first value that

    * is popped. Stacks therefore operate in a last-in-first-out (LIFO)* order. For maximum generality, the Stack class is defined using* a template that allows the client to define a stack that contains* any type of value, as in Stack or Stack.*/

    template class Stack {

    public:

    /** Constructor: Stack* Usage: Stack stack;* ------------------------* The constructor initializes a new empty stack containing* the specified value type.*/

    Stack();

    /** Destructor: ~Stack* Usage: (usually implicit)* -------------------------* The destructor deallocates any heap storage associated* with this stack.*/

    ~Stack();

    /** Method: size* Usage: nElems = stack.size();* -----------------------------* Returns the number of elements in this stack.*/

    int size();

  • 7/31/2019 11 Linear Structures

    5/28

    Linear Structures 387

    /** Method: isEmpty* Usage: if (stack.isEmpty()) . . .* ---------------------------------* Returns true if this stack contains no elements, and false* otherwise.

    */bool isEmpty();

    /** Method: clear* Usage: stack.clear();* ---------------------* This method removes all elements from this stack.*/

    void clear();

    /** Method: push

    * Usage: stack.push(elem);* ------------------------* Pushes the specified element onto this stack.*/

    void push(ElemType elem);

    /** Method: pop* Usage: topElem = stack.pop();* -----------------------------* Removes the top element from this stack and returns it.* Raises an error if called on an empty stack.*/

    ElemType pop();

    /** Method: peek* Usage: topElem = stack.peek();* ------------------------------* Returns the value of top element from this stack without* removing it. Raises an error if called on an empty stack.*/

    ElemType peek();

    private:

    #include "stackpriv.h"};

    #include "stackimpl.cpp"

    #endif

  • 7/31/2019 11 Linear Structures

    6/28

    Linear Structures 388

    Figure 11-3 Implementation of theStack

    class using the array-based representation

    /** File: stackimpl.cpp* -------------------* This file contains the array-based implementation of the* Stack class.*/

    #ifdef _stack_h

    /** Implementation notes: Stack constructor* ---------------------------------------* The constructor must allocate the array storage for the stack* elements and initialize the fields of the object.*/

    template Stack::Stack() {

    capacity = INITIAL_CAPACITY;elements = new ElemType[capacity];count = 0;

    }

    Figure 11-2 Private section of the Stack class for the array-based representation

    /** File: stackpriv.h* -----------------* This file contains the private section of the Stack template* class. Including this information in a separate file means

    * that clients don't need to look at these details.*/

    /** Implementation notes: Stack data structure* ------------------------------------------* The elements of the stack are stored in a dynamic array of* the specified element type. If the space in the array is ever* exhausted, the implementation doubles the array capacity.*/

    /* Constants */

    static const int INITIAL_CAPACITY = 100;

    /* Instance variables */

    ElemType *elements; /* A dynamic array of the elements */int capacity; /* The allocated size of the array */int count; /* The number of elements on the stack */

    /* Private method prototypes */

    void expandCapacity();

  • 7/31/2019 11 Linear Structures

    7/28

    Linear Structures 389

    /** Implementation notes: pop, peek* -------------------------------* These methods must check for an empty stack and report an* error if there is no top element.*/

    template ElemType Stack::pop() {

    if (isEmpty()) {Error("pop: Attempting to pop an empty stack");

    }return elements[--count];

    }

    template ElemType Stack::peek() {

    if (isEmpty()) {Error("peek: Attempting to peek at an empty stack");

    }

    return elements[count - 1];}

    /** Implementation notes: expandCapacity* ------------------------------------* This private method doubles the capacity of the elements array* whenever it runs out of space. To do so, it must allocate a new* array, copy all the elements from the old array to the new one,* and free the old storage.*/

    template void Stack::expandCapacity() {

    capacity *= 2;ElemType *oldElements = elements;elements = new ElemType[capacity];for (int i = 0; i < count; i++) {

    elements[i] = oldElements[i];}delete[] oldElements;

    }

    Pushing a new element onto the stack adds that element at the beginning of the chain.The steps involved are the same as those required to insert a character into a linked-listbuffer. You first allocate a new cell, then enter the data, and, finally, update the linkpointers so that the new cell becomes the first element in the chain. Thus, if you push the

    element e2 on the stack, you get the following configuration:

    stack

    e2

    NULL

    e1

    In the linked-list representation, the pop operation consists of removing the first cell inthe chain and returning the value stored there. Thus, a pop operation from the stack

  • 7/31/2019 11 Linear Structures

    8/28

    Linear Structures 390

    Figure 11-4 Private section of the Stack class for the list-based representation

    /** File: stackpriv.h* -----------------* This file contains the private section for the list-based* implementation of the Stack class. Including this section* in a separate file means that clients don't need to look* at these details.*/

    /* Type for linked list cell */

    struct cellT {ElemType data;cellT *link;

    };

    /* Instance variables */

    cellT *list; /* Beginning of the list of elements */int count; /* Number of elements in the stack */

    shown in the preceding diagram returns e2 and restores the previous state of the stack, asfollows:

    NULL

    stack

    e1

    The data structure for the Stack class now consists of a single instance variable usedto store the start of the linked list representing the stack. The private section of the Stacktherefore consists of the structure definition for the cellT type, the pointer to the start ofthe list, and an integer that holds the number of elements so that the implementationdoesnt have to count them each time. The contents of the revised stackpriv.h file areshown in Figure 11-4.

    Once you have defined the data structure, you can then move on to reimplement theStack class methods so that they operate on the new data representation. The firstfunction that needs to change is the constructor, which must initialize the start field toNULL and the count field to 0, like this:

    template Stack::Stack() {

    start = NULL;count = 0;

    }

    The remaining methods are equally straightforward. The revised implementation of thepush method must allocate a new cell and then add it to the front of the list. The popmethod removes the first cell from the list and returns the value that cell contained. Sincethese operations only change a few pointers at the start of the list, both can be performedin constant time. The complete implementation of the Stack class using linked lists isshown in Figure 11-5.

  • 7/31/2019 11 Linear Structures

    9/28

    Linear Structures 391

    Figure 11-5 Implementation of the Stack class using the list-based representation

    /** File: stackimpl.cpp* -------------------* This file contains the list-based implementation of the* Stack class.

    */

    #ifdef _stack_h

    /** Implementation notes: Stack constructor* ---------------------------------------* The constructor must create an empty linked list and then* initialize the fields of the object.*/

    template Stack::Stack() {

    list = NULL;count = 0;

    }

    /** Implementation notes: ~Stack destructor* ---------------------------------------* The destructor frees any memory allocated by the implementation.* Freeing this memory guarantees that the stack abstraction* will not "leak memory" in the process of running an* application. Because clear frees each element it processes,* this implementation of the destructor uses that method.*/

    template Stack::~Stack() {

    clear();}

    /** Implementation notes: size, isEmpty, clear* ------------------------------------------* These implementations should be self-explanatory.*/

    template int Stack::size() {

    return count;}

    template

    bool Stack::isEmpty() {return count == 0;}

    template void Stack::clear() {

    while (count > 0) {pop();

    }}

  • 7/31/2019 11 Linear Structures

    10/28

    Linear Structures 392

    /** Implementation notes: push* --------------------------* This method chains a new element onto the list* where it becomes the top of the stack.*/

    template void Stack::push(ElemType elem) {

    cellT *cell = new cellT;cell->data = elem;cell->link = list;list = cell;count++;

    }

    /** Implementation notes: pop, peek* -------------------------------* These methods must check for an empty stack and report an

    * error if there is no top element. The pop method must free* the cell to ensure that the implementation does not waste* heap storage as it executes.*/

    template ElemType Stack::pop() {

    if (isEmpty()) {Error("pop: Attempting to pop an empty stack");

    }cellT *cell = list;ElemType result = cell->data;list = list->link;count--;

    delete cell;return result;

    }

    template ElemType Stack::peek() {

    if (isEmpty()) {Error("peek: Attempting to peek at an empty stack");

    }return list->data;

    }

    #endif

  • 7/31/2019 11 Linear Structures

    11/28

    Linear Structures 393

    11.3 Implementing queuesAs you know from Chapter 4, stacks and queues are very similar structures. The onlydifference between them is in the order in which elements are processes. A stack uses alast-in/first-out (LIFO) discipline in which the last item pushed is always the first itempopped. A queue adopts a first-in/first-out (FIFO) model that more closely resembles awaiting line. The interfaces for stacks and queues are also extremely similar. As you can

    see from the public section of the queue.h interface in Figure 11-6, the only things thathave changed from the stack.h interface in Figure 11-1 are the names of two methods(push is now enqueue and pop is dequeue) and the comments that describe howelements in each structure are ordered.

    Given the conceptual similarity of these structures and their interfaces, it is probablynot surprising that both stacks and queues can be implemented using either an array-based or list-based strategy. With each of these models, however, the implementation ofa queue has subtleties that dont arise in the case of a stack. These differences arise fromthe fact that all the operations on a stack occur at the same end of the internal datastructure. In a queue, the enqueue operation happens at one end, and the dequeueoperation happens at the other.

    An array-based implementation of queues

    In light of the fact that actions in a queue are no longer confined to one end of an array,you need two indices to keep track of the head and tail positions in the queue. Theprivate instance variables therefore look like this:

    ElemType *elements; /* A dynamic array of the elements */int capacity; /* The allocated size of the array */int head; /* The index of the head of the queue */int tail; /* The index of the tail of the queue */

    In this representation, the head field holds the index of the next element to come out ofthe queue, and the tail field holds the index of the next free slot. In an empty queue, itis clear that the tail field should be 0 to indicate the initial position in the array, but whatabout the head field? For convenience, the traditional strategy is to set the head field to 0as well. When queues are defined in this way, having the head and tail fields be equalindicates that the queue is empty.

    Given this representation strategy, the Queue constructor looks like this:

    template Queue::Queue() {

    head = tail = 0;}

    Although it is tempting to think that the enqueue and dequeue methods will lookalmost exactly like their push and pop counterparts in the Stack class, you will run into

    several problems if you simply try to copy the existing code. As is often the case inprogramming, it makes more sense to use diagrams to make sure you understand exactlyhow the queue should operate before you start writing the code.

    To get a sense of how this representation of a queue works, imagine that the queuerepresents a waiting line, similar to one in the simulation from Chapter 4. From time totime, a new customer arrives and is added to the queue. Customers waiting in line areperiodically served at the head end of the queue, after which they leave the waiting lineentirely. How does the queue data structure respond to each of these operations?

  • 7/31/2019 11 Linear Structures

    12/28

    Linear Structures 394

    Figure 11-6 Interface for the Queue class

    /** File: queue.h* -------------* This interface defines a general queue abstraction that uses* templates so that it can work with any element type.

    */

    #ifndef _queue_h#define _queue_h

    /** Template class: Queue* -------------------------------* This class template models a queue, which is a linear collection* of values that resemble a waiting line. Values are added at* one end of the queue and removed from the other. The fundamental* operations are enqueue (add to the tail of the queue) and dequeue* (remove from the head of the queue). Because a queue preserves* the order of the elements, the first value enqueued is the first

    * value dequeued. Queues therefore operate in a first-in-first-out* (FIFO) order. For maximum generality, the Queue class is defined* using a template that allows the client to define a queue that* contains any type of value, as in Queue or Queue.*/

    template class Queue {

    public:

    /** Constructor: Queue* Usage: Queue queue;* ------------------------* The constructor initializes a new empty queue containing* the specified value type.*/

    Queue();

    /** Destructor: ~Queue* Usage: (usually implicit)* -------------------------* The destructor deallocates any heap storage associated* with this queue.*/

    ~Queue();

    /** Method: size* Usage: nElems = queue.size();* -----------------------------* Returns the number of elements in this queue.*/

    int size();

  • 7/31/2019 11 Linear Structures

    13/28

    Linear Structures 395

    /** Method: isEmpty* Usage: if (queue.isEmpty()) . . .* ---------------------------------* Returns true if this queue contains no elements, and false* otherwise.

    */

    bool isEmpty();

    /** Method: clear* Usage: queue.clear();* ---------------------* This method removes all elements from this queue.*/

    void clear();

    /*

    * Method: enqueue* Usage: queue.enqueue(elem);* ---------------------------* Adds the specified element to the end of this queue.*/

    void enqueue(ElemType elem);

    /** Method: dequeue* Usage: first = queue.dequeue();* -------------------------------* Removes the first element from this queue and returns it.* Raises an error if called on an empty queue.

    */

    ElemType dequeue();

    /** Method: peek* Usage: topElem = queue.peek();* ------------------------------* Returns the value of first element from this queue without* removing it. Raises an error if called on an empty queue.*/

    ElemType peek();

    #include "queuepriv.h"};

    #include "queueimpl.cpp"

    #endif

  • 7/31/2019 11 Linear Structures

    14/28

    Linear Structures 396

    Assuming that the queue is empty at the beginning, its internal structure looks like this:

    0 1 2 3 4 5 6 7 8 9

    elements

    head tail0 0

    Suppose now that five customers arrive, indicated by the letters A through E. Thosecustomers are enqueued in order, which gives rise to the following configuration:

    A B C D E

    0 1 2 3 4 5 6 7 8 9

    elements

    head tail

    0 5

    The value 0 in the head field indicates that the first customer in the queue is stored inposition 0 of the array; the value 5 in tail indicates that the next customer will be placedin position 5. So far, so good. At this point, suppose that you alternately serve acustomer at the beginning of the queue and then add a new customer to the end. Forexample, customerA is dequeued and customer Farrives, which leads to the followingsituation:

    B C D E F

    0 1 2 3 4 5 6 7 8 9

    elements

    head tail

    1 6

    Imagine that you continue to serve one customer just before the next customer arrives andthat this trend continues until customerJarrives. The internal structure of the queue thenlooks like this:

    F G H I J

    0 1 2 3 4 5 6 7 8 9

    elements

    head tail

    5 1 0

    At this point, youve got a bit of a problem. There are only five customers in thequeue, but you have used up all the available space. The tail field is pointing beyondthe end of the array. On the other hand, you now have unused space at the beginning ofthe array. Thus, instead of incrementing tail so that it indicates the nonexistent position10, you can wrap around from the end of the array back to position 0, as follows:

  • 7/31/2019 11 Linear Structures

    15/28

    Linear Structures 397

    F G H I J

    0 1 2 3 4 5 6 7 8 9

    elements

    head tail

    5 0

    From this position, you have space to enqueue customer Kin position 0, which leads tothe following configuration:

    K F G H I J

    0 1 2 3 4 5 6 7 8 9

    elements

    head tail

    5 1

    If you allow the elements in the queue to wrap around from the end of the array to the

    beginning, the active elements always extend from the head index up to the positionimmediately preceding the tail index, as illustrated in this diagram:

    K F G H I J

    0 1 2 3 4 5 6 7 8 9

    Because the ends of the array act as if they were joined together, programmers call thisrepresentation a ring buffer.

    The only remaining issue you need to consider before you can write the code forenqueue and dequeue is how to check whether the queue is completely full. Testing for

    a full queue is trickier than you might expect. To get a sense of where complicationsmight arise, suppose that three more customers arrive before any additional customers areserved. If you enqueue the customersL,M, andN, the data structure looks like this:

    K L M N F G H I J

    0 1 2 3 4 5 6 7 8 9

    elements

    head tail

    5 4

    At this point, it appears as if there is one extra space. What happens, though, if customerO arrives at this moment? If you followed the logic of the earlier enqueue operations,

    you would end up in the following configuration:

    K L M N O F G H I J

    0 1 2 3 4 5 6 7 8 9

    elements

    head tail

    5 5 This queue looks empty.

  • 7/31/2019 11 Linear Structures

    16/28

    Linear Structures 398

    Figure 11-7 Implementation of the Queue class using the array-based representation

    /** File: queueimpl.cpp* -------------------* This file contains the array-based implementation of the* Queue class.*/

    #ifdef _queue_h

    /** Implementation notes: Queue constructor* ---------------------------------------* The constructor must allocate the array storage for the queue* elements and initialize the fields of the object.

    */

    template Queue::Queue() {

    capacity = INITIAL_CAPACITY;elements = new ElemType[capacity];head = 0;tail = 0;

    }

    /** Implementation notes: ~Queue destructor* ---------------------------------------* The destructor frees any memory that is allocated by the

    * implementation. Freeing this memory guarantees the client* that the queue abstraction will not "leak memory" in the* process of running an application.*/

    template Queue::~Queue() {

    delete[] elements;}

    The queue array is now completely full. Unfortunately, whenever the head and tailfields have the same value, as they do in this diagram, the queue is considered to beempty. There is no way to tell from the contents of the queue structure itself which of thetwo conditionsempty or fullactually applies, because the data values look the samein each case. Although you can fix this problem by adopting a different definition for theempty queue and writing some special-case code, the traditional approach is to limit the

    number of elements in the queue to one less than the number of elements in the array. andto expand the capacity whenever that slightly lower limit is reached.

    The code for the array implementation of the Queue class template is shown in Figure11-7. It is important to observe that the code does not explicitly test the array indices tosee whether they wrap around from the end of the array to the beginning. Instead, thecode makes use of the % operator to compute the correct index automatically. Thetechnique of using remainders to reduce the result of a computation to a small, cyclicalrange of integers is an important mathematical technique called modular arithmetic.

  • 7/31/2019 11 Linear Structures

    17/28

    Linear Structures 399

    /** Implementation notes: size* --------------------------* The size of the queue can be calculated from the head and tail* indices by using modular arithmetic.*/

    template int Queue::size() {

    return (tail + capacity - head) % capacity;}

    /** Implementation notes: isEmpty* -----------------------------* The queue is empty whenever the head and tail pointers are* equal. Note that this interpretation means that the queue* cannot be allowed to fill the capacity entirely and must* always leave one unused space.*/

    template bool Queue::isEmpty() {

    return head == tail;}

    /** Implementation notes: clear* ---------------------------* The clear method need not take account of where in the* ring buffer any existing data is stored and can simply* set the head and tail index back to the beginning.*/

    template void Queue::clear() {head = tail = 0;

    }

    /** Implementation notes: enqueue* -----------------------------* This method must first check to see whether there is* enough room for the element and expand the array storage* if necessary. Because it is otherwise impossible to* differentiate the case when a queue is empty from when* it is completely full, this implementation expands the* queue when the size is one less than the capacity.

    */

    template void Queue::enqueue(ElemType elem) {

    if (size() == capacity - 1) expandCapacity();elements[tail] = elem;tail = (tail + 1) % capacity;

    }

  • 7/31/2019 11 Linear Structures

    18/28

    Linear Structures 400

    /** Implementation notes: dequeue, peek* -----------------------------------* These methods must check for an empty queue and report an* error if there is no first element.*/

    template ElemType Queue::dequeue() {

    if (isEmpty()) {Error("dequeue: Attempting to dequeue an empty queue");

    }ElemType result = elements[head];head = (head + 1) % capacity;return result;

    }

    template ElemType Queue::peek() {

    if (isEmpty()) {

    Error("peek: Attempting to peek at an empty queue");}return elements[head];

    }

    /** Implementation notes: expandCapacity* ------------------------------------* This private method doubles the capacity of the elements array* whenever it runs out of space. To do so, it must allocate a new* array, copy all the elements from the old array to the new one,* and free the old storage. Note that this implementation also* shifts all the elements back to the beginning of the array.*/

    template void Queue::expandCapacity() {

    int count = size();capacity *= 2;ElemType *oldElements = elements;elements = new ElemType[capacity];for (int i = 0; i < count; i++) {

    elements[i] = oldElements[(head + i) % capacity];}head = 0;tail = count;delete[] oldElements;

    }

    The one file in the array-based queue definition that has not yet been specified is thequeuepriv.h file that contains the private data for this class. Although you alreadyknow the instance variables it contains, it is worth showing the contents of the file as anillustration of the sort of comments that go into the private section. This section definesthe underlying data representation and is therefore logically part of the implementation.The primary audience for any comments included in the queuepriv.h file is that set ofprogrammers who may need to maintain this code. Those comments should includeanything special or complex about the representation, as illustrated in Figure 11-8.

  • 7/31/2019 11 Linear Structures

    19/28

    Linear Structures 401

    Figure 11-8 Private section of the Queue class for the array-based representation

    /** File: queuepriv.h* -----------------* This file contains the private section of the Queue template* class. Including this information in a separate file means

    * that clients don't need to look at these details.*/

    /** Implementation notes: Queue data structure* ------------------------------------------* The array-based queue stores the elements in successive index* positions in an array, just as a stack does. What makes the* queue structure more complex is the need to avoid shifting* elements as the queue expands and contracts. In the array* model, this goal is achieved by keeping track of both the* head and tail indices. The tail index increases by one each* time an element is enqueued, and the head index increases by* one each time an element is dequeued. Each index therefore* marches toward the end of the allocated array and will* eventually reach the end. Rather than allocate new memory,* this implementation lets each index wrap around back to the* beginning as if the ends of the array of elements were joined* to form a circle. This representation is called a ring buffer.** The elements of the queue are stored in a dynamic array of* the specified element type. If the space in the array is ever* exhausted, the implementation doubles the array capacity.* Note that the queue capacity is reached when there is still* one unused element in the array. If the queue is allowed to* fill completely, the head and tail indices will have the same* value, and the queue will appear empty.*/

    /* Constants */

    static const int INITIAL_CAPACITY = 100;

    /* Instance variables */

    ElemType *elements; /* A dynamic array of the elements */int capacity; /* The allocated size of the array */int head; /* The index of the head of the queue */int tail; /* The index of the tail of the queue */

    /* Private method prototypes */

    void expandCapacity();

  • 7/31/2019 11 Linear Structures

    20/28

    Linear Structures 402

    Figure 11-9 Private section of the Queue class for the list-based representation

    /** File: queuepriv.h* -----------------* This file contains the private section for the list-based

    * implementation of the Queue class. Including this section* in a separate file means that clients don't need to look* at these details.*/

    /** Implementation notes: Queue data structure* ------------------------------------------* The list-based queue uses a linked list to store the elements* of the queue. To ensure that adding a new element to the tail* of the queue is fast, the data structure maintains a pointer* to the last cell in the queue as well as the first. If the* queue is empty, the tail pointer is always set to be NULL.*

    * The following diagram illustrates the structure of a queue* containing two elements, A and B.** +-------+ +-------+ +-------+* head | o---+------->| A | +--==>| B |* +-------+ +-------+ | | +-------+* tail | o---+---+ | o---+--+ | | NULL |* +-------+ | +-------+ | +-------+* | |* +------------------+*/

    /* Type for linked list cell */

    struct cellT {ElemType data;cellT *link;

    };

    /* Instance variables */

    cellT *head; /* Pointer to the cell at the head */cellT *tail; /* Pointer to the cell at the tail */int count; /* Number of elements in the queue */

    Linked-list representation of queues

    The queue class also has a simple representation using list structure. To illustrate thebasic approach, the elements of the queue are stored in a list beginning at the head of thequeue and ending at the tail. To allow both enqueue and dequeue to run in constanttime, the Queue object must keep a pointer to both ends of the queue. The privateinstance variables are therefore defined as shown in the revised version ofqueuepriv.h

    shown in Figure 11-9. The data diagram drawn in characters in queuepriv.h is likely toconvey more information to the implementer than the surrounding text. Such diagramsare difficult to produce, but they offer enormous value to the reader.

    Given a modern word processor and a drawing program, it is possible to produce muchmore detailed diagrams than you can make using ASCII characters alone. If you are

  • 7/31/2019 11 Linear Structures

    21/28

    Linear Structures 403

    designing data structures for a large and complex system, it probably makes sense tocreate these diagrams and include them as part of the extended documentation of apackage, ideally on a web page. Here, for example, is a somewhat more readable pcitureof a queue containing the customersA, B, and C:

    tail

    A B C

    queue

    head

    The code for the linked-list implementation of queues appears in Figure 11-6. On thewhole, the code is reasonably straightforward, particularly if you use the linked-listimplementation of stacks as a model. The diagram of the internal structure provides theessential insights you need to understand how to implement each of the queue operations.

    The enqueue operation, for example, adds a new cell after the one marked by the tailpointer and then updates the tail pointer so that it continues to indicate the end of thelist. The dequeue operation consists of removing the cell addressed by the head pointerand returning the value in that cell.

    The only place where the implementation gets tricky is in the representation of theempty queue. The most straightforward approach is to indicate an empty queue bystoring NULL in the head pointer, as follows:

    tail

    queue

    head

    The enqueue implementation must check for the empty queue as a special case. If thehead pointer is NULL, enqueue must set both the head and tail pointers so that theypoint to the cell containing the new element. Thus, if you were to enqueue the customerA into an empty queue, the internal structure of the pointers at the end of the enqueueoperation would look like this:

    tail

    A

    queue

    head

    If you make another call to enqueue, the head pointer is no longer NULL, which meansthat the implementation no longer has to perform the special-case action for the emptyqueue. Instead, the enqueue implementation uses the tail pointer to find the end of the

  • 7/31/2019 11 Linear Structures

    22/28

    Linear Structures 404

    Figure 11-10 Implementation of the Queue class using the list-based representation

    /** File: queueimpl.cpp* -------------------* This file contains the list-based implementation of the* Queue class.*/

    #ifdef _queue_h

    /** Implementation notes: Queue constructor* ---------------------------------------* The constructor must create an empty linked list and then* initialize the fields of the object.

    */template Queue::Queue() {

    head = tail = NULL;count = 0;

    }

    /** Implementation notes: ~Queue destructor* ---------------------------------------* The destructor frees any memory that is allocated by the* implementation. Freeing this memory guarantees the client* that the queue abstraction will not "leak memory" in the

    * process of running an application. Because clear frees* each element it processes, this implementation of the* destructor simply calls that method.*/

    template Queue::~Queue() {

    clear();}

    linked-list chain and adds the new cell at that point. For example, if you enqueue thecustomerB after customerA, the resulting structure looks like this:

    tail

    A B

    queue

    head

    A complete implementation of the list-based queue structure appears in Figure 11-10.

  • 7/31/2019 11 Linear Structures

    23/28

    Linear Structures 405

    /** Implementation notes: size* --------------------------* In order to return the size in constant time, it is necessary* to store the count in the data structure and keep it updated* on each call to enqueue and dequeue.

    */template int Queue::size() {

    return count;}

    /** Implementation notes: isEmpty* -----------------------------* This code uses the traditional head == tail test for an empty* stack; testing the value of count would work just as well.*/

    template

    bool Queue::isEmpty() {return head != tail;

    }

    /** Implementation notes: clear* ---------------------------* This code calls dequeue to make sure the cells are freed.*/

    template void Queue::clear() {

    while (count > 0) {dequeue();

    }}

    /** Implementation notes: enqueue* -----------------------------* This method allocates a new list cell and chains it in* at the tail of the queue. If the queue is currently empty,* the new cell must also become the head pointer in the queue.*/

    template void Queue::enqueue(ElemType elem) {

    cellT *cell = new cellT;

    cell->data = elem;cell->link = NULL;if (head == NULL) {

    head = cell;} else {

    tail->link = cell;}tail = cell;count++;

    }

  • 7/31/2019 11 Linear Structures

    24/28

    Linear Structures 406

    /** Implementation notes: dequeue, peek* -----------------------------------* These methods must check for an empty queue and report an* error if there is no first element. The dequeue method* must also check for the case in which the queue becomes

    * empty and set both the head and tail pointers to NULL.*/

    template ElemType Queue::dequeue() {

    if (isEmpty()) {Error("dequeue: Attempting to dequeue an empty queue");

    }cellT *cell = head;ElemType result = cell->data;head = cell->link;if (head == NULL) tail = NULL;count--;delete cell;

    return result;}

    template ElemType Queue::peek() {

    if (isEmpty()) {Error("peek: Attempting to peek at an empty queue");

    }return tail->data;

    }

    #endif

    Summary

    In this chapter, you have learned how to use the C++ template mechanism for genericcontainer classes. A template allows you to define the class in terms of a typeplaceholder that can be specialized to a particular client data type. You have also had thechance to see a list-based implementation of the Stack class and two differentimplementations of the Queue class, one that uses arrays for its underlying storage andone that uses a linked list.

    Important points in this chapter include:

    Templates are used to define generic container classes.

    Stacks can be implemented using a linked-list structure in addition to the moretraditional array-based representation.

    The array-based implementation of queues is somewhat more complex than its stackcounterpart. The traditional implementation uses a structure called a ring buffer, inwhich the elements logically wrap around from the end of the array to the beginning.Modular arithmetic makes it easy to implement the ring buffer concept.

    In the ring-buffer implementation used in this chapter, a queue is considered emptywhen its head and tail indices are the same. This representation strategy means that themaximum capacity of the queue is one element less than the allocated size of the array.Attempting to fill all the elements in the array makes a full queue indistinguishablefrom an empty one.

  • 7/31/2019 11 Linear Structures

    25/28

    Linear Structures 407

    Queues can also be represented using a singly linked list marked by two pointers, oneto the head of the queue and another to the tail.

    Review questions

    1. When designing a generic container, what advantages does a C++ template offer?

    2. When specializing a class template for use as a client, how do you specify what typeshould be used to fill in the template placeholder?

    3. Draw a linked-list diagram of the stackmyStack after the following operations areperformed:

    Stack myStack;myStack.push('A');myStack.push('B');myStack.push('C');

    4. What are the expanded forms of the acronyms LIFO and FIFO? Which of thesedisciplines pertains to the queue abstraction?

    5. What are the names of the fundamental queue operations?

    6. If you use an array to store the underlying elements in a queue, what are the Queueclass private instance variables?

    7. What is a ring buffer? How does the ring-buffer concept apply to queues?

    8. How can you tell if an array-based queue is empty? How can you tell if it hasreached its capacity?

    9. Assuming that INITIAL_CAPACITY has the artificially small value 3, draw a diagramshowing the underlying representation of the array-based queuemyQueue after thefollowing sequence of operations:

    Queue myQueue;myQueue.enqueue('A');myQueue.enqueue('B');myQueue.enqueue('C');myQueue.dequeue();myQueue.dequeue();myQueue.enqueue('D');myQueue.enqueue('E');myQueue.dequeue();myQueue.enqueue('F');

    10. Explain how modular arithmetic is useful in the array-based implementation ofqueues.

    11. Describe what is wrong with the following implementation ofsize for the array-based representation of queues:

    template int Queue::size() {

    return (tail - head) % QueueArraySize;}

  • 7/31/2019 11 Linear Structures

    26/28

    Linear Structures 408

    12. Draw a diagram showing the internal structure of a linked-list queue after thecomputer finishes the set of operations in question 9.

    13. How can you tell if a linked-list queue is empty?

    Programming exercises

    1. One of the principal reasons that stacks are usually implemented using arrays is thatlinked lists impose a significant memory overhead to store the pointers. You can,however, reduce this cost by adopting a hybrid approach similar to the one describedin Chapter 10, exercise 13. The idea is to represent the stack as a linked list ofblocks, each of which contains a fixed array of elements. Whenever a stack block isexhausted, a new block can be added to the front of a chain in the data structure toopen up the necessary additional space. For example, if there were four elements perblock, a stack into which the integers 1 through 9 had been pushed in numericalorder would look like this:

    67

    8

    23

    4

    9 5 1

    Write a new implementation of the Stack class that uses this design. Note that theprivate instance variables are not shown in the diagram and are left for you to design.

    2. Because the ring-buffer implementation of queues makes it impossible to tell thedifference between an empty queue and one that is completely full, the capacity ofthe queue is one less than the allocated size of the array. You can avoid thisrestriction by changing the internal representation so that the concrete structure ofthe queue keeps track of the number of elements in the queue instead of the index of

    the tail element. Given the index of the head element and the number of data valuesin the queue, you can easily calculate the tail index, which means that you dont needto store this value explicitly. Rewrite the array-based queue representation so that ituses this representation.

    3. In exercise 9 from Chapter 4, you had the opportunity to write a function

    void ReverseQueue(Queue & queue);

    that reverses the elements in the queue, working entirely from the client side. If youare the designer of a class, however, you could add this facility to the queue.hinterface and export it as one of its methods. For both the array- and list-basedimplementations of the queue, make all the changes necessary to export the method

    void Queue::reverse();

    that reverses the elements in the queue. In both cases, write the functions so thatthey use the original memory cells and do not allocate any additional storage.

    4. In the queue abstraction presented in this chapter, new items are always added at theend of the queue and wait their turn in line. For some programming applications, itis useful to extend the simple queue abstraction into a priority queue,in which theorder of the items is determined by a numeric priority value. When an item isenqueued in a priority queue, it is inserted in the list ahead of any lower priority

  • 7/31/2019 11 Linear Structures

    27/28

    Linear Structures 409

    Table 11-1 Methods exported by the simplified Vector class

    size() Returns the number of elements in the vector.

    isEmpty() Returns true if the vector is empty.

    getAt(index) Returns the element of the vector that appears at the specifiedindex position.

    setAt(index,value) Sets the element at the specified index to the new value.Attempting to reset an element outside the bounds of thevector generates an error.

    add(value) Adds a new element at the end of the vector.

    insertAt(index,value) Inserts the new value before the specified index position.

    removeAt(index) Deletes the element at the specified index position.

    clear() Removes all elements from the vector.

    items. If two items in a queue have the same priority, they are processed in thestandard first-in/first-out order.

    Extend the linked-list implementation of queues so that it supports priorityqueues. To do so, you need to add a new function to the interface with the followingprototype:

    void Queue::enqueueWithPriority(ElemType element,int priority);

    The parameter element is the same as for enqueue; the additional priorityargument is an integer representing the priority. As in conventional English usage,smaller integers correspond to higher priorities, so that priority 1 comes beforepriority 2, and so forth.

    Keep in mind that you are implementing an extension to an existing interface.Clients who do not use the enqueueWithPriority extension should not need tomake any changes in their code.

    5. Use the techniques introduced in this chapter to implement a slightly simplifiedversion of the Vector class that incorporates all the facilities from the library model

    with the exception of iteration, selection using square brackets, and deep copying.Your first step is to write the vector.h interface in which you infer the prototypesfor the methods from the list of methods listed in Table 11-1 (which is just a copy ofthe similar table from Chapter 4 without the iterator method and bracket notationfeatures, for which you do not yet have the necessary tools). For this problem, use adynamic array as the underlying representation, and make sure that your arrayexpands as necessary.

    6. Reimplement the vector.h interface from the preceding exercise so that it uses alinked list as its underlying representation. What operations are slower using thismodel? What operations are faster? How might you design the data structure so thatthe add method always executes in constant time?

  • 7/31/2019 11 Linear Structures

    28/28

    This page intentionally left blank