STACKS & QUEUES =============== Both are like lists (ordered, homogeneous, contiguous collection of items), but with more restricted operations. --------- | STACK | --------- conceptual picture: trays in a cafeteria _______ _________ / \ / \ values in \/ / \/ | ----- | values out | | | ----- | | | | ----- | | | | | ---------- The only value that can be taken out (or even seen) is the MOST RECENTLY ADDED Operations (1) Initialize (create an empty stack) constructor fn (2) Add an item to the "top" Push (3) Remove and return top item Pop (4) Ask if stack is empty Empty Linked-List representation -------------------------- . very similar to List implementation, but . don't need "current" (can only access top of stack) . don't need "last" ( " ) . "first" points to top of stack (so call it top) . don't need header node (that was for easier AddToEnd, AddInOrder) template class Stack { public: Stack (); // constructor Stack (const Stack & S); // copy constructor ~ Stack (); // destructor void Push (T item); T Pop (); bool Empty (); Stack & operator = (const Stack & S); // assignment private: struct StackNode { T data; StackNode * next; }; StackNode * top; }; Operations ---------- (1) Initialize (constructor fn) . make stack empty template Stack::Stack(): top(NULL) { } (2) Push . add given item to top of stack (front of list) template void Stack::Push(T item) { StackNode * tmp = new StackNode; tmp -> data = item; tmp -> next = top; top = tmp; } Note: works for empty stack, too! 1 (3) Pop . error if stack empty . save data from top-of-stack node . remove top node from stack (free storage) . return saved value template T Stack::Pop( ) { T retval; StackNode * tmp = top; assert (! Empty( )); retval = top -> data; top = top -> next; delete tmp; return (retval); } (4) Empty . return true iff stack is empty template bool Stack::Empty( ) { return (NULL == top); } * still to do (for you): copy constructor operator = destructor Times for operations -------------------- . destructor, copy constructor, operator= are all 0(# items in stack) . all others 0(1) But: use of new and delete are expensive! One trick: maintain your own "freelist": . when a node is popped, put the node on the freelist (do not delete it) . when a node is pushed - before calling new, see if freelist non-empty makes code more complicated, but avoids lots of new and deletes . freelist node order irrelevant - so just manipulate like stack: always add, remove from front Array representation -------------------- . :) no need for new, delete . :( must worry about full stack . for many applications, stack tends to be small (i.e., pushes matched by pops), so array is ok . can allow stack size to be set at declaration time (could do this for array implementation of List, too) template class Stack { private: T * stackItems; // this will be the actual array int top; // index of top-of-stack item int max; // size of array, which is max # of items public: const int Default Size = 100; // max size of stack if none specified Stack (int max Size = Default Size); // constructor Stack (const Stack & S); // copy constructor ~Stack ( ) // destructor Stack & operator = (const Stack & S) // assignment }; Notes: (1) The declaration of the constructor function takes advantage of the fact that C++ allows default values to be given for parameters. If the actual declaration of a Stack includes a size, this value is ignored, but if not, this value is used. (2) This implementation DOES have a pointer as a data member, so it is important to provide a copy constructor, a destructor, and operator=. Here's what happens when a Stack is declared: . declare a stack w/ max size: Stack S(50); . constructor fn. will allocate array of int of size 50, pointed to by the stackItems data member of the Stack class: S.stackItems: -> [0] [1] . . . [49] . C++ lets you treat the data member "stackItems" (which is a pointer) as if it were an array: S.stackItems[0] <- 1st space in array " [1] <- 2nd " etc., Note: the user of the stack will not write this kind of code it will be in the (private) member fns. And here's how it actually happens (here's the constructor function): template Stack :: Stack (int maxSize): stackItems (new T [maxSize]), top (-1), // no items yet, so top is -1 max (maxSize) { } Notes: (1) Only give the parameter a default value in ".h" file, not here. (2) The expression: "new T [maxSize]" means allocate an array of size maxSize, of items of type T. Here are the other member functions: template void Stack :: Push (T item) { assert (top < max-1); top ++; stack Items [top] = item; } template bool Stack :: Empty ( ) { return (-1 == top); } template T Stack :: Pop ( ) { assert (! Empty ( )); top--; return (stackItems [top+1]; } template Stack :: ~Stack ( ) // destructor fn: return allocated storage { delete [ ] stack Items; } Note: the expression: "delete []" must be used when multiple items were allocated; no need to give actual # here (in fact, not legal!) template stack :: Stack (const Stack & S): // copy constructor: must allocate NEW array and copy all values from // the parameter S to the new array (to avoid aliasing) stackItems (new T [S.max]), // allocate new storage max (S.max) // max size same as S's max size { for (top = 0; top < max; top ++){ stackItems [top] = S.stackItems[top]; } top = S.top; } Similarly, operator = must . free old storage (if wrong size) and allocate new storage . copy values Array representation time requirements ====================================== Operation time constructor 0(1) copy constructor 0(# items in copied stack) destructor 0(1) operator = 0(# items in copied stack) Empty/Push/Pop 0(1) Summary (new ideas): ==================== . can use new to allocate space for a whole array of items . syntax: = new T [<#of items>] delete [ ] . exploit this to allow array to be allocated @ declaration time different stacks can be of different sizes (but they can still become full ...) . Note: now class uses ptr / involves dynamically allocated storage so must provide destructor copy constructor operation = --------- | QUEUE | --------- . similar to stack, but first in first out (FIFO) instead of last in first out (LIFO) . conceptual picture is people waiting in line: first come, first served . operations: 1. initialize to empty constructor fn 2. add item (to rear of queue) Enqueue 3. remove and return item Dequeue (from front of queue) 4. ask if queue is empty Empty Linked-list Implementation -------------------------- . similar to List implementation . need both "first" and "last" (call them "front" and "rear") since Enqueue/Dequeue work on opposite ends of the list . to get 0(1) time for both Enqueue & Dequeue: Enqueue (add items)) to end of list Dequeue (remove items) from front of list Note: Dequeue is the key operation; we can add in 0(1) time to either end, but can only remove from front in 0(1) time w/o making doubly-linked list or maintaining a "second-to-the-end" ptr Array Implementation -------------------- First Try Enqueue same as Add To End . assert queue not full . increment "rear" index . store new value in array time is 0(1) Dequeue . assert queue not empty . save value in [0] . move all other values "one step forward" . return saved value time is 0 (# items in queue) <- | we can do better! Second Try Idea: . don't force front-of-queue item to be in [0] let it "move up" as items are dequeued . maintain front and rear indexes . front = index of 1st item in queue rear = index of 1st empty space following last item in queue example: [0] [1] 3 front = 1 [2] 5 [3] 2 [4] rear = 4 Q: what should happen to "rear" in this example if we Enqueue one more item? A: let it "wrap around" (and similarly for "front" eventually) (this is sometimes called a "circular array" implementation) Notes: (1) We can determine queue-full, queue-empty from relative positions of front, rear but this is tricky! Easier to maintain another data member: size. Empty when size == 0 Full when size == max (as for Stack - set max on declaration, increment/decrement as part of Enqueue/Dequeue) (2) Enqueue/Dequeue code can't just increment rear/front must handle wrap-around: template void Stack::Enqueue (T item) { assert (! Full ( ) ); size ++; queueItems[rear] = item; if (rear == max-1) rear = 0; else rear++; or rear++; rear = rear % max; ^-- modulus a % b is remainder of a/b } ... similar for Dequeue ... Enqueue . assert queue not full . store new value in array . "increment" rear index . increment size time is 0(1) Dequeue . assert queue not empty . "increment" front index . decrement size . return value one place "before" front index time is 0(1) Empty . true iff size == 0 time is 0(1) Initialize . set size to 0 . set front to 0 . set read to 0 time is 0(1) Stack/Queue Applications ======================== Stack . managing functions at runtime especially recursive functions . evaluation of postfix expressions (book 3.3) . some graph operations (coming soon ...) Queue . graph/tree operations (coming soon ...) . simulations (book 3.4.3)