Stacks and Queues Andy Wang Data Structures, Algorithms, and Generic Programming Abstract Data Type A collection of data A set of operations on the data or subsets of the data A set of axioms, or rules of behavior governing the interaction of operators Examples: stack, queue, list, vector, deque, priority queue, table (map), associative array, set, graph, digraph Stack ADT Collections: Elements of some proper type T Operations: void push(T t) void pop() T top() bool empty() unsigned int size() constructor and destructor Stack ADT (2) Axioms (for any stack S) S.size, S.empty(), and S.push(t) are always defined S.pop() and S.top() are defined iff S.empty() is false S.empty(), S.size(), S.top() do not change S S.empty() is true iff S.size() == 0 S.push(t) followed by S.pop() leaves S unchanged Stack ADT (3) Axioms (for any stack S) After S.push(t), S.top() returns t S.push(t) increases S.size() by 1 S.pop() decreases S.size() by 1 Stack Model—LIFO Empty stack S S.empty() is true S.top() not defined S.size() == 0 food chain stack Stack Model—LIFO S.push(“mosquito”) S.empty() is false S.top() == “mosquito” S.size() == 1 mosquito food chain stack Stack Model—LIFO S.push(“fish”) S.empty() is false S.top() == “fish” S.size() == 2 fish mosquito food chain stack Stack Model—LIFO S.push(“raccoon”) S.empty() is false S.top() == “raccoon” S.size() == 3 raccoon fish mosquito food chain stack Stack Model—LIFO S.pop() S.empty() is false S.top() == “fish” S.size() == 2 fish mosquito food chain stack Derivable Behaviors (Theorems) If (S.size() == n) is followed by k push operations, then S.size() == n + k If (S.size() == n) is followed by k pop operations, then S.size == n – k (k <= n) The last element of S pushed onto S is the top of S S.pop() removes the last element of S pushed onto S Uses of ADT Stack Depth first search / backtracking Evaluating postfix expressions Converting infix to postfix Function calls (runtime stack) Recursion Queue ADT Collection Elements of some proper type T Operations void push(T t) void pop() T front() bool empty() unsigned int size() Constructors and destructors Queue ADT Axioms (for any Queue Q) Q.size(), Q.empty(), Q.push(t) are always defined Q.pop() and Q.front() are defined iff Q.empty() is false Q.empty(), Q.size(), Q.front() do not change Q Q.empty() is true iff Q.size() == 0 Suppose Q.size() == n, and the next element pushed onto Q is t; then, after n elements have been popped from Q, t = Q.front() Queue ADT Axioms (for any Queue Q) Q.push(t) increases Q.size() by 1 Q.pop() decreases Q.size() by 1 If t = Q.front() then Q.pop() removes t from Q Queue Model—FIFO Empty Q animal parade queue Queue Model—FIFO Q.Push(“ant”) animal parade queue ant front back Queue Model—FIFO Q.Push(“bee”) animal parade queue ant bee front back Queue Model—FIFO Q.Push(“cat”) animal parade queue ant bee cat front back Queue Model—FIFO Q.Push(“dog”) animal parade queue ant bee cat dog front back Queue Model—FIFO Q.Pop() animal parade queue bee cat dog front back Queue Model—FIFO Q.Pop() animal parade queue cat dog front back Queue Model—FIFO Q.Push(“eel”) Q.Pop() Q.Pop() animal parade queue eel front back Derivable Behaviors (Theorems) If (Q.size() == n) is followed by k push operations, then Q.size() == n + k If (Q.size() == n) is followed by k pop operations, then Q.size() == n – k (k <= n) The first element pushed onto Q is the the front of Q Q.pop() removes the front element of Q Uses of ADT Queue Buffers Breadth first search Simulations Depth First Search—Backtracking Problem Discover a path from start to goal Solution start 1 Go deep If there is an unvisited neighbor, go there Backtrack Retreat along the path to find an unvisited neighbor Outcome If there is a path from start to goal, DFS finds one such path 5 2 3 4 6 7 8 9 10 goal 11 12 Depth First Search—Backtracking (2) Stack start 1 5 Push 1 2 3 4 6 7 8 9 10 goal 11 12 Depth First Search—Backtracking (3) Stack start 1 5 Push Push 2 1 2 3 4 6 7 8 9 10 goal 11 12 Depth First Search—Backtracking (4) Stack start 1 5 Push Push Push 5 2 1 2 3 4 6 7 8 9 10 goal 11 12 Depth First Search—Backtracking (5) Stack start 1 Push Push Push Push 6 5 2 1 5 2 3 4 6 7 8 9 10 goal 11 12 Depth First Search—Backtracking (6) Stack start 1 Push Push Push Push Push 9 6 5 2 1 5 2 3 4 6 7 8 9 10 goal 11 12 Depth First Search—Backtracking (7) Stack start 1 Pop Push Push Push Push 6 5 2 1 5 2 3 4 6 7 8 9 10 goal 11 12 Depth First Search—Backtracking (8) Stack start 1 2 3 4 6 7 8 9 10 Pop 5 Push Push Push 5 2 1 goal 11 12 Depth First Search—Backtracking (9) Stack start 1 5 2 3 4 6 7 8 9 10 Pop Push Push 2 1 goal 11 12 Depth First Search—Backtracking (10) Stack start 1 5 Pop Push 1 2 3 4 6 7 8 9 10 goal 11 12 Depth First Search—Backtracking (11) Stack start 1 5 Push Push 3 1 2 3 4 6 7 8 9 10 goal 11 12 Depth First Search—Backtracking (12) Stack start 1 5 Push Push Push 7 3 1 2 3 4 6 7 8 9 10 goal 11 12 Depth First Search—Backtracking (13) Stack start 1 Push Push Push Push 10 7 3 1 5 2 3 4 6 7 8 9 10 goal 11 12 DFS Implementation DFS { stack<location> S; // mark the start location as visited S.push(start); while (S is not empty) { t = S.top(); if (t == goal) Success(S); if (// t has unvisited neighbors) { // choose an unvisited neighbor // mark n visited; S.push(n); } else { BackTrack(S); } } Failure(S); } DFS Implementation (2) BackTrack(S) { while (!S.empty() && S.top() has no unvisited neighbors) { S.pop(); } } Success(S) { // print success while (!S.empty()) { output(S.top()); S.pop(); } } Failure(S) { // print failure while (!S.empty()) { S.pop(); } } Breadth First Search Problem Find a shortest path from start to goal start 1 5 2 3 4 6 7 8 9 10 goal 11 12 Breadth First Search (2) Queue 1 Push start 1 5 2 3 4 6 7 8 9 10 goal 11 12 Breadth First Search (3) Queue Pop start 1 5 2 3 4 6 7 8 9 10 goal 11 12 Breadth First Search (4) Queue 2 3 4 Push Push Push start 1 5 2 3 4 6 7 8 9 10 goal 11 12 Breadth First Search (5) Queue 3 4 Pop start 1 5 2 3 4 6 7 8 9 10 goal 11 12 Breadth First Search (6) Queue 3 4 5 6 Push Push start 1 5 2 3 4 6 7 8 9 10 goal 11 12 Breadth First Search (7) Queue 4 5 6 Pop start 1 5 2 3 4 6 7 8 9 10 goal 11 12 Breadth First Search (8) Queue 4 5 6 7 8 Push Push start 1 5 2 3 4 6 7 8 9 10 goal 11 12 Breadth First Search (9) Queue 5 6 7 8 Pop start 1 5 2 3 4 6 7 8 9 10 goal 11 12 Breadth First Search (10) Queue 6 7 8 Pop start 1 5 2 3 4 6 7 8 9 10 goal 11 12 Breadth First Search (11) Queue 7 8 Pop start 1 5 2 3 4 6 7 8 9 10 goal 11 12 Breadth First Search (12) Queue 7 8 9 Push start 1 5 2 3 4 6 7 8 9 10 goal 11 12 Breadth First Search (13) Queue 8 9 Pop start 1 5 2 3 4 6 7 8 9 10 goal 11 12 Breadth First Search (14) Queue 8 9 10 Push start 1 5 2 3 4 6 7 8 9 10 goal 11 12 BFS Implementation BFS { queue<location> Q; // mark the start location as visited Q.push(start); while (Q is not empty) { t = Q.front(); for (// each unvisited neighbor n) { Q.push(n); if (n == goal) Success(S); } Q.pop(); } Failure(Q); } Evaluating Postfix Expressions Postfix expressions: operands precede operator Tokens: atomics of expressions, either operator or operand Example: z = 25 + x*(y – 5) Tokens: z, =, 25, +, x, *, (, y, -, 5, ) Evaluating Postfix Expressions (2) Evaluation algorithm: Use stack of tokens Repeat If operand, push onto stack If operator • pop operands off stack • evaluate operator on operands • push result onto stack Until expression is read Return top of stack Evaluating Postfix Expressions (3) Most CPUs have hardware support for this algorithm Translation from infix to postfix also uses a stack (software) Evaluating Postfix Expressions (4) Original expression: 1 + (2 + 3) * 4 + 5 Evaluate: 1 2 3 + 4 * + 5 + Evaluating Postfix Expressions (5) Input: 1 2 3 + 4 * + 5 + Push(1) 1 Evaluating Postfix Expressions (6) Input: 2 3 + 4 * + 5 + Push(2) 2 1 Evaluating Postfix Expressions (7) Input: 3 + 4 * + 5 + Push(3) 3 2 1 Evaluating Postfix Expressions (8) Input: + 4 * + 5 + Pop() == 3 Pop() == 2 1 Evaluating Postfix Expressions (9) Input: + 4 * + 5 + Push(2 + 3) 5 1 Evaluating Postfix Expressions (10) Input: 4 * + 5 + Push(4) 4 5 1 Evaluating Postfix Expressions (11) Input: * + 5 + Pop() == 4 Pop() == 5 1 Evaluating Postfix Expressions (12) Input: * + 5 + Push(5 * 4) 20 1 Evaluating Postfix Expressions (13) Input: + 5 + Pop() == 20 Pop() == 1 Evaluating Postfix Expressions (14) Input: + 5 + Push(1 + 20) 21 Evaluating Postfix Expressions (15) Input: 5 + Push(5) 5 21 Evaluating Postfix Expressions (16) Input: + Pop() == 21 Pop() == 5 Evaluating Postfix Expressions (17) Input: + Push(21 + 5) 26 Evaluating Postfix Expressions (18) Input: Pop() == 26 Postfix Evaluation Implementation Evaluate(postfix expression) { // use stack of tokens; while(// expression is not empty) { t = next token; if (t is operand) { // push onto stack } else { // pop operands for t off stack // evaluate t on these operands // push result onto stack } } // return top of stack } Runtime Stack Runtime environment Static heap Executable code Global variables Stack Push for each function call Pop for each function return Heap Dynamic new and delete stack static program memory Recursion Order 1: function calls itself Order 2: f() calls g(), and g() calls f() Facilitated by stack Adaptor Class Adapts the public interface of another class Adaptee: the class being used Adaptor: the new class being defined Uses protected object of the adaptee type Uses the adaptee’s methods to define adaptor methods Stack and Queue implemented via adaptor classes Stack Adaptor Requirements Stack PushBack() PopBack() Back() Empty() Size() Can use TDeque, TList, TVector Queue Adaptor Requirements Queue PushBack() PopFront() Front() Empty() Size() Can use TDeque, TList Class CStack template <typename T, class Container> class CStack { protected: Container c; public: typedef T value_type; void Push(const value_type& x) { c.PushBack(x); } void Pop() { c.PopBack(); } value_type Top() const { return c.Back(); } int Empty() const { return c.Empty(); } unsigned int Size() const { return c.Size(); } void Clear() { c.Clear(); } void Display(ostream& os, char ofc) const; }; Class CStack (2) template <typename T, class Container> void CStack<T, Container>::Display(std::ostream& os, char ofc) const { typename Container::Iterator I; if (ofc == ‘\0’) { for (I = c.Begin(); I != c.End(); ++I) { os << *I; } } else { for (I = c.Begin(); I != c.End(); ++I) { os << *I << ofc; } } } template <typename T, class Container> std::ostream& operator<<(std::ostream& os, const CStack<T, Container>& s) { s.Display(os); return os; } Declarations CStack<float, TList<float> > floatStack; CStack<int, TDeque<int> > intStack; Class CQueue template <typename T, class Container> class CQueue { protected: Container c; public: typedef T value_type; void Push(const value_type& x) { c.PushBack(x); } void Pop() { c.PopFront(); } value_type Front() const { return c.Front(); } int Empty() const { return c.Empty(); } unsigned int Size() const { return c.Size(); } void Clear() { c.Clear(); } void Display(ostream& os, char ofc) const; }; Class CQueue (2) template <typename T, class Container> void CQueue<T, Container>::Display(std::ostream& os, char ofc) const { typename Container::Iterator I; if (ofc == ‘\0’) { for (I = c.Begin(); I != c.End(); ++I) { os << *I; } } else { for (I = c.Begin(); I != c.End(); ++I) { os << *I << ofc; } } } template <typename T, class Container> std::ostream& operator<<(std::ostream& os, const CQueue<T, Container>& s) { s.Display(os); return os; } Example Usage Functionality Testing fcstack.cpp fcqueue.cpp Performance Testing qrace.cpp srace.cpp dragstrp.h timer.h datetime.h xran.h