Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Stacks and Queues 1 Abstract Data Types (ADTs) • An abstract data type (ADT) is an abstraction of a data structure • An ADT specifies: – Data stored – Operations on the data – Error conditions associated with operations 2 The Stack ADT • The Stack ADT stores arbitrary objects. • Insertions and deletions follow the last-in first-out (LIFO) scheme. • It is like a stack of trays: – Trays can be added to the top of the stack. – Trays can be removed from the top of the stack. • Main stack operations: – push(object o): inserts element o – pop(): removes and returns the last inserted element 3 push pop top Stack 4 The Stack ADT • Auxiliary stack operations: – top(): returns a reference to the last inserted element without removing it – size(): returns the number of elements stored – isEmpty(): returns a Boolean value indicating whether no elements are stored 5 Exceptions • Attempting the execution of an operation of ADT may sometimes cause an error condition, called an exception • Exceptions are said to be “thrown” by an operation that cannot be executed • In the Stack ADT, operations pop and top cannot be performed if the stack is empty • Attempting the execution of pop or top on an empty stack throws an EmptyStackException. 6 Applications of Stacks • Direct applications – Page-visited history in a Web browser – Undo sequence in a text editor – Saving local variables when one function calls another, and this one calls another, and so on. • Indirect applications – Auxiliary data structure for algorithms – Component of other data structures 7 Stacks and Computer Languages • • A stack can be used to check for unbalanced symbols (e.g. matching parentheses) Algorithm 1. Make an empty stack. 2. Read symbols until the end of file. a. If the token is an opening symbol, push it onto the stack. b. If it is a closing symbol and the stack is empty, report an error. c. Otherwise, pop the stack. If the symbol popped is not the corresponding opening symbol, report an error. 3. At the end of the file, if the stack is not empty, report an error. 8 C++ Run-time Stack • The C++ run-time system keeps main() { track of the chain of active int i = 5; functions with a stack foo(i); • When a function is called, the run} time system pushes on the stack a frame containing foo(int j) { – Local variables and return int k; value k = j+1; – Program counter, keeping track bar(k); of the statement being executed } • When a function returns, its frame bar(int m) { is popped from the stack and … control is passed to the method on } top of the stack bar PC = 1 m=6 foo PC = 3 j=5 k=6 main PC = 2 i=5 9 Array-based Stack • A simple way of implementing the Stack ADT uses an array. • We add elements from left to right. • A variable keeps track of the index of the top element Algorithm size() return t + 1 Algorithm pop() if isEmpty() then throw EmptyStackException else t t 1 return S[t + 1] … S 0 1 2 t 10 Array-based Stack (cont.) • The array storing the stack elements may become full • A push operation will then throw a FullStackException Algorithm push(o) if t = S.length 1 then throw FullStackException else – Limitation of the arrayt t+1 based implementation S[t] o – Not intrinsic to the Stack ADT … S 0 1 2 t 11 Performance and Limitations • Performance – Let n be the number of elements in the stack – The space used is O(n) – Each operation runs in time O(1) • Limitations – The maximum size of the stack must be defined a priori , and cannot be changed – Trying to push a new element into a full stack causes an implementation-specific exception 12 Stack Interface in C++ template <class Object> class Stack { public: Stack(int c = 1000); int size() const; bool isEmpty( ) const; const Object & top()const throw(StackEmptyException); void push(const Object & x) throw(StackFullException); Object pop() throw(StackEmptyException); private: int capacity; // stack capacity Object *S; // stack array int top; // top of stack }; 13 Array-based Stack in C++ // Stack class implementation Stack(int c) { capacity = c; S = new Object[capacity]; top = –1; } int size() const { return (top + 1); } bool isEmpty() const { return (top < 0); } 14 Stack Implementation (cont.) Object& top() throw(StackEmptyException) { if (isEmpty()) throw StackEmptyException("Access to empty stack"); return S[top]; } void push(const Object& elem) throw(StackFullException) { if (size() == capacity) throw StackFullException("Stack overflow"); S[++top] = elem; } Object pop() throw(StackEmptyException) { if (isEmpty()) throw StackEmptyException("Access to empty stack"); return S[top--]; } 15 Example • Reading a line of text and writing it out backwards. int main( ) { Stack<char> s; char c; while ((c=getchar() )!=’\n’) s.push(c); while( !s.isEmpty( ) ) cout << s.pop( ) << endl; return 0; } 16 A Simple Calculator • Calculators can evaluate infix expressions, such as 5 + 2. • In an infix expression a binary operator has arguments to its left and right. – e.g. 1+2*3 9 – 5 –3 2^3^2 • When there are several operators, precedence and associativity determine how the operators are processed. 10 – 3 – 2 ^ 3 * 4 / 5 / 10 ^ 2 17 Postfix Machines • In a postfix expression a binary operator follows its operands. – e.g. 52+ 123*+ 10 3 – 2 3 ^ 4 * 5 / 10 2 ^ / - • A postfix expression can be evaluated as follows: – Operands are pushed into a single stack. – An operator pops its operands and then pushes the result. – At the end of the evaluation, the stack should contain only one element, which represents the result. 18 Example • Evaluate the following postfix expression. 8 5 4 * 5 6 2 / + – 2 / + 19 Linked list implementation of Stacks • In implementing Stack as a linked list the top of the stack is represented by the first item in the linked list. • To implement push: create a new node and attach it as the new first node. • To implement pop: advance the top of stack to the second item in the list (if there is one). • Each operation is performed in constant time. • See chapter 16 for details. 20 The Abstract Data Type Queue • A queue is a list from which items are deleted from one end (front) and into which items are inserted at the other end (rear, or back) – It is like line of people waiting to purchase tickets: • Queue is referred to as a first-in-first-out (FIFO) data structure. – The first item inserted into a queue is the first item to leave • Queues have many applications in computer systems: – Any application where a group of items is waiting to use a shared resource will use a queue. e.g. • jobs in a single processor computer • print spooling • information packets in computer networks. 21 A Queue enqueue dequeue front rear 22 ADT Queue Operations • createQueue() – Create an empty queue • destroyQueue() – Destroy a queue • isEmpty():boolean – Determine whether a queue is empty • enqueue(in newItem:QueueItemType) – Inserts a new item at the end of the queue (at the rear of the queue) • dequeue() throw QueueException dequeue(out queueFront:QueueItemType) – Removes (and returns) the element at the front of the queue • getFront(out queueFront:QueueItemType) – Retrieve the item that was added earliest (without removing) 23 Some Queue Operations Operation operation Queue after x.createQueue() an empty queue front x.enqueue(5) x.enqueue(3) x.enqueue(2) x.dequeue() x.enqueue(7) x.dequeue(a) x.getFront(b) 5 5 5 3 3 2 2 3 3 2 2 2 7 7 7 (a is 3) (b is 2) 24 An Application -- Reading a String of Characters • A queue can retain characters in the order in which they are typed aQueue.createQueue() while (not end of line) { Read a new character ch aQueue.enqueue(ch) } • Once the characters are in a queue, the system can process them as necessary 25 Recognizing Palindromes • A palindrome – A string of characters that reads the same from left to right as its does from right to left • To recognize a palindrome, a queue can be used in conjunction with a stack – A stack reverses the order of occurrences – A queue preserves the order of occurrences • A nonrecursive recognition algorithm for palindromes – As you traverse the character string from left to right, insert each character into both a queue and a stack – Compare the characters at the front of the queue and the top of the stack 26 Recognizing Palindromes (cont.) The results of inserting a string into both a queue and a stack 27 Recognizing Palindromes isPal(in str:string) : boolean // Determines whether str is a palindrome or not aQueue.createQueue(); aStack.createStack(); len = length of str; for (i=1 through len) { nextChar = ith character of str; aQueue.enqueue(nextChar); aStack.push(nextChar); } charactersAreEqual = true; while (aQueue is not empty and charactersAreEqual) { aQueue.getFront(queueFront); aStack.getTop(stackTop); if (queueFront equals to stackTop) { aQueue.dequeue(); aStack.pop()}; } else chractersAreEqual = false; } 28 return charactersAreEqual; Implementations of the ADT Queue • Pointer-based implementations of queue – A linear linked list with two external references • A reference to the front • A reference to the back – A circular linked list with one external reference • A reference to the back • Array-based implementations of queue – A naive array-based implementation of queue – A circular array-based implementation of queue 29 Pointer-based implementations of queue a linear linked list with two external pointers a circular linear linked list with one external pointer 30 Pointer-Based Implementation -enqueue Inserting an item into a nonempty queue Inserting an item into an empty queue a) before insertion b) after insertion 31 Pointer-Based Implementation -- dequeue Deleting an item from a queue of more than one item Deleting an item from a queue with one item tempPtr before deletion tempPtr = frontPtr; frontPtr = NULL; backPtr = NULL; delete tempPtr; after deletion 32 Header File #include "QueueException.h" typedef desired-type-of-queue-item QueueItemType; class Queue { public: Queue(); // default constructor Queue(const Queue& Q); // copy constructor ~Queue(); // destructor bool isEmpty() const; // Determines whether the queue is empty. void enqueue(QueueItemType newItem); // Inserts an item at the back of a queue. void dequeue() throw(QueueException); // Dequeues the front of a queue. // Retrieves and deletes the front of a queue. void dequeue(QueueItemType& queueFront) throw(QueueException); // Retrieves the item at the front of a queue. void getFront(QueueItemType& queueFront) const 33 throw(QueueException); Header File private: // The queue is implemented as a linked list with one external pointer // to the front of the queue and a second external pointer to the back // of the queue. struct QueueNode { QueueItemType item; QueueNode *next; }; // end struct QueueNode *backPtr; QueueNode *frontPtr; } 34 constructor, deconstructor, isEmpty #include "QueueP.h" // header file Queue::Queue() : backPtr(NULL), frontPtr(NULL){} // default constructor Queue::~Queue() { // destructor while (!isEmpty()) dequeue(); // backPtr and frontPtr are NULL at this point } bool Queue::isEmpty() const{ return backPtr == NULL; } // isEmpty 35 enqueue void Queue::enqueue(QueueItemType newItem) { // create a new node QueueNode *newPtr = new QueueNode; // enqueue // set data portion of new node newPtr->item = newItem; newPtr->next = NULL; // insert the new node if (isEmpty()) // insertion into empty queue frontPtr = newPtr; else // insertion into nonempty queue backPtr->next = newPtr; backPtr = newPtr; // new node is at back } 36 dequeue void Queue::dequeue() throw(QueueException) { if (isEmpty()) throw QueueException("QueueException: empty queue, cannot dequeue"); else { // queue is not empty; remove front QueueNode *tempPtr = frontPtr; if (frontPtr == backPtr) { // one node in queue frontPtr = NULL; backPtr = NULL; } else frontPtr = frontPtr->next; tempPtr->next = NULL; delete tempPtr; }} // defensive strategy 37 dequeue, getFront void Queue::dequeue(QueueItemType& queueFront) throw(QueueException) { if (isEmpty()) throw QueueException("QueueException: empty queue, cannot dequeue"); else { // queue is not empty; retrieve front queueFront = frontPtr->item; dequeue(); // delete front }} void Queue::getFront(QueueItemType& queueFront) const throw(QueueException) { if (isEmpty()) throw QueueException("QueueException: empty queue, cannot getFront"); else // queue is not empty; retrieve front queueFront = frontPtr->item; } 38 A circular linked list with one external pointer Queue Operations constructor ? isEmpty ? enqueue ? dequeue ? getFront ? 39 A Naive Array-Based Implementation of Queue • Rightward drift can cause the queue to appear full even though the queue contains few entries. • We may shift the elements to left in order to compensate for rightward drift, but shifting is expensive • Solution: A circular array eliminates rightward drift. 40 Circular Array Implementation • The front and rear are the same as the basic model, except: The queue wraps around when the end of the array is reached. rear front 1 8 3 7 0 1 8 9 2 3 4 5 6 7 41 A Circular Array-Based Implementation When either front or back advances past MAX_QUEUE-1 it wraps around to 0. 42 The effect of some operations of the queue Initialize: front=0; back=MAX_QUEUE-1; Insertion : back = (back+1) % MAX_QUEUE; items[back] = newItem; NOT ENOUGH Deletion : front = (front+1) % MAX_QUEUE; 43 PROBLEM – Queue is Empty or Full front and back cannot be used to distinguish between queue-full and queue-empty conditions. ? Empty (back+1)%MAX_QUEUE == front ? Full (back+1)%MAX_QUEUE == front So, we need extra mechanism to distinguish between queue-full and queue-empty conditions. 44 Solutions for Queue-Empty/Queue-Full Problem 1. Using a counter to keep the number items in the queue. • Initialize count to 0 during creation; Increment count by 1 during insertion; Decrement count by 1 during deletion. • count=0 empty; count=MAX_QUEUE full 2. Using isFull flag to distinguish between the full and empty conditions. • When the queue becomes full, set isFullFlag to true; When the queue is not full set isFull flag to false; 3. Using an extra array location (and leaving at least one empty location in the queue). ( MORE EFFICIENT ) • Declare MAX_QUEUE+1 locations for the array items, but only use MAX_QUEUE of them. We do not use one of the array locations. • Full: front equals to (back+1)%(MAX_QUEUE+1) • Empty: front equals to back 45 Using a counter • To initialize the queue, set – front to 0 – back to MAX_QUEUE–1 – count to 0 • Inserting into a queue back = (back+1) % MAX_QUEUE; items[back] = newItem; ++count; • Deleting from a queue front = (front+1) % MAX_QUEUE; --count; • Full: count == MAX_QUEUE • Empty: count == 0 46 Array-Based Implementation Using a counter – Header File #include "QueueException.h" const int MAX_QUEUE = maximum-size-of-queue; typedef desired-type-of-queue-item QueueItemType; class Queue { public: Queue(); // default constructor bool isEmpty() const; void enqueue(QueueItemType newItem) throw(QueueException); void dequeue() throw(QueueException); void dequeue(QueueItemType& queueFront) throw(QueueException); void getFront(QueueItemType& queueFront) const throw(QueueException); private: QueueItemType items[MAX_QUEUE]; int front; int back; int count; }; 47 constructor, isEmpty, enqueue Queue::Queue():front(0), back(MAX_QUEUE-1), count(0) {} bool Queue::isEmpty() const { return count == 0); } void Queue::enqueue(QueueItemType newItem) throw(QueueException) { if (count == MAX_QUEUE) throw QueueException("QueueException: queue full on enqueue"); else { // queue is not full; insert item back = (back+1) % MAX_QUEUE; items[back] = newItem; ++count; } 48 } dequeue void Queue::dequeue() throw(QueueException) { if (isEmpty()) throw QueueException("QueueException: empty queue, cannot dequeue"); else { // queue is not empty; remove front front = (front+1) % MAX_QUEUE; --count; }} void Queue::dequeue(QueueItemType& queueFront) throw(QueueException) { if (isEmpty()) throw QueueException("QueueException: empty queue, cannot dequeue"); else { // queue is not empty; retrieve and remove front queueFront = items[front]; front = (front+1) % MAX_QUEUE; --count; }} 49 dequeue void Queue::getFront(QueueItemType& queueFront) const throw(QueueException) { if (isEmpty()) throw QueueException("QueueException: empty queue, cannot getFront"); else // queue is not empty; retrieve front queueFront = items[front]; } 50 Using isFull flag • To initialize the queue, set front = 0; false; back = MAX_QUEUE–1; isFull = • Inserting into a queue back = (back+1) % MAX_QUEUE; items[back] = newItem; if ((back+1)%MAX_QUEUE == front)) isFull = true; • Deleting from a queue front = (front+1) % MAX_QUEUE; isFull = false; • Full: isFull == true • Empty: isFull==false front)) && ((back+1)%MAX_QUEUE == 51 Using an extra array location full queue empty queue • To initialize the queue, allocate (MAX_QUEUE+1) locations front=0; back=0; • front holds the index of the location before the front of the queue. • Inserting into a queue (if queue is not full) back = (back+1) % (MAX_QUEUE+1); items[back] = newItem; • Deleting from a queue (if queue is not empty) front = (front+1) % (MAX_QUEUE+1); • Full: (back+1)%(MAX_QUEUE+1) == front Empty: front == back • 52 Comparing Implementations • Fixed size versus dynamic size – A statically allocated array • Prevents the enqueue operation from adding an item to the queue if the array is full – A resizable array or a reference-based implementation • Does not impose this restriction on the enqueue operation • Pointer-based implementations – A linked list implementation • More efficient; no size limit 53 A Summary of Position-Oriented ADTs • Position-oriented ADTs: List, Stack, Queue • Stacks and Queues – Only the end positions can be accessed • Lists – All positions can be accessed • Stacks and queues are very similar – Operations of stacks and queues can be paired off as • createStack and createQueue • Stack isEmpty and queue isEmpty • push and enqueue • pop and dequeue • Stack getTop and queue getFront 54