Data Structures and Algorithms Alice E. Fischer Lecture 6: Stacks 2018 Alice E. Fischer Data Structures L5, Stacks... 1/29 Lecture 6: Stacks 2018 1 / 29
Outline 1 Stacks C++ Template Class Functions 2 Stack Applications 3 Stack Application: Parsing 4 Infix, Postfix, and Prefix Expressions Alice E. Fischer Data Structures L5, Stacks... 2/29 Lecture 6: Stacks 2018 2 / 29
Stacks Stacks A stack is a linear LIFO data structure. All linear data structures have a beginning and an end, but in a stack, access is restricted to one end. All data is added and removed at the beginning of a stack. No action happens at the end. Alice E. Fischer Data Structures L5, Stacks... 3/29 Lecture 6: Stacks 2018 3 / 29
Stacks Applications of Stacks A stack is LIFO. LIFO situations are rare in the real world, but they do happen. But stacks are essential in the world of computing. All modern languages use a stack to manage allocation and deallocation of function parameters and local variable. Each function call puts a frame on top of the run-time stack and each function return removes a frame. The stack frames provide an ever-adapting context for the code. A stack is an essential tool for implementing recursion. A stack is also used during program translation. The nested control statements and curly braces are matched up and validated by the compiler using a stack. The familiar infix notation for expressions is translated, using a stack, to postfix notation that a computer can evaluate. Alice E. Fischer Data Structures L5, Stacks... 4/29 Lecture 6: Stacks 2018 4 / 29
Stacks Implementing a Stack Two kinds of stacks are illustrated here. First is a stack implemented by an ordinary array. It is simple and fast, but has limited capacity. Stack s; s.push('a'); s.push('z'); s.push('m'); s.push('p'); max top data a z m p?????? [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] max 10 4 10 top 4 [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] data a z m p?????? Second is a stack implemented by a growing array. It is almost as simple and almost as fast. Alice E. Fischer Data Structures L5, Stacks... 5/29 Lecture 6: Stacks 2018 5 / 29
Stacks A Linked-List Stack This stack is implemented by a linked list. It can grow, as needed, but takes more memory and more run time than an array-based stack. Stack s; s.push('a'); s.push('z'); s.push('m'); s.push('p'); top count 4 p m z a nullptr Alice E. Fischer Data Structures L5, Stacks... 6/29 Lecture 6: Stacks 2018 6 / 29
Stacks Stack and Queue Implementations These structures can be implemented in an array, a growing array, or a linked list. Each implementation has its own and advantages / disadvantages. Stack Queue Array simple, fast, fast, limited size. limited size. Growing fast, fast, Array not complex, complex to program unlimited size unlimited size Linked easy not difficult List slower runtime slower runtime unlimited size unlimited size The nature of the application you are implementing may determine which implementation you should use. Alice E. Fischer Data Structures L5, Stacks... 7/29 Lecture 6: Stacks 2018 7 / 29
Stacks Traditional and Current Stack Function Names There is a long tradition behind the names of the functions used with stacks and queues. However, those names were invented long before OO, and are no longer useful. C++ provides template class implementations that honor some of these names, change some, add alternative names and provide additional functions. Traditional C++ Template Action for Stack S top() back() Access item at the top of S. push() push() Add item to the top of S pop() pop() Remove item from top of S empty() empty() Is S empty? full() Is S full? size() How many items are in S? emplace() Construct an item and add to top of S. Alice E. Fischer Data Structures L5, Stacks... 8/29 Lecture 6: Stacks 2018 8 / 29
Stacks C++ Template Class Functions stack<bt> is an adapter class It is built on top of another container that can be a vector<bt>, a list<bt> or a deque<bt>. A deque (double-ended queue) is the default. stack<int> st1; stack<int, vector<int>> st2; while (!st2.empty())... unsigned n = st2.size(); BT temp = st2.top( ); st2.push( item ); st2.emplace( BT( ) ); st2.pop(); swap( st1, st2); // Empty stack using deque // Using vector // Result is type bool // # of elements in container // Returns reference to stack top // Insert item above top of stack // Construct and push an item // Remove item at top of stack. Return is void // Swaps two entire stacks Alice E. Fischer Data Structures L5, Stacks... 9/29 Lecture 6: Stacks 2018 9 / 29
Stack Applications Stack Applications Reversing the order of any sequence Matching nested pairs of symbols In every compiler: Parsing source code Run time memory management Implementing recursion Alice E. Fischer Data Structures L5, Stacks... 10/29 Lecture 6: Stacks 2018 10 / 29
Stack Applications Reversing a Sequence Example: Base conversion. The algorithm to generate digits in the new base is easy, but it generates the least significant digit first. Solution: put the digits on a stack and print it when the algorithm finishes the conversion. Program: BaseConversion Alice E. Fischer Data Structures L5, Stacks... 11/29 Lecture 6: Stacks 2018 11 / 29
Stack Applications Code: Reversing a Sequence int n; int base; Stack<int> st; // input: the number to convert // input: base to which we will convert n // Store sequence of converted digits cin >> n >> base; // Generate digits of converted number, right to left. for (int power=0; n!= 0; ++power) { st.push( n % base ); // Isolate right-hand digit of n. n /= base; // then eliminate right-hand digit. } // Print digits in opposite order of generation. st.print(cout); Alice E. Fischer Data Structures L5, Stacks... 12/29 Lecture 6: Stacks 2018 12 / 29
Stack Applications Matching Nested Pairs Example: Matching brackets Many text-file formats, including program code and html, require tags to come in well-nested pairs. Solution: As the file is read, search for left- or open-symbols. When found, classify each one as a left-token and push onto a stack. Process the text following the open-symbols. When the right- or close-symbol arrives, compare it to the left-symbol at the top of the stack. If they match, a unit of text has been identified. If they don t match, there is a syntax error. Alice E. Fischer Data Structures L5, Stacks... 13/29 Lecture 6: Stacks 2018 13 / 29
Stack Applications Code: Classifying Tokens enum BracketType{BKT_SQ,BKT_RND,BKT_CURLY,BKT_ANGLE,BKT_NONE}; enum TokenSense{SENSE_LEFT, SENSE_RIGHT}; void Token:: classify( char ch ){ static const char* brackets = "[](){}<>"; char* p = strchr( brackets, ch ); if (p==null) { } type = BKT_NONE; sense = SENSE_LEFT; } else { } // arbitrary value int pos = p-brackets; // pointer - gives subscript. sense = (pos % 2 == 0)? SENSE_LEFT : SENSE_RIGHT; type = (BracketType)(pos/2); // int /, with truncation. Alice E. Fischer Data Structures L5, Stacks... 14/29 Lecture 6: Stacks 2018 14 / 29
Stack Applications Code: Matching brackets for (;;) { // Read and process the file. in.get(ch); // Don t skip leading whitespace. if (in.eof()) break; Token curtok( ch ); if (curtok.gettype()==bkt_none) continue; // skip non-tags switch (curtok.getsense()) { case SENSE_LEFT: stk.push( curtok ); break; case SENSE_RIGHT: if (stk.empty()) mismatch("too many right tags", curtok, false); toptok = stk.peek(); if (toptok.gettype()!= curtok.gettype()) mismatch("right tag has wrong type", curtok, false); stk.pop(); // Open- and close- tags match. break; }} Alice E. Fischer Data Structures L5, Stacks... 15/29 Lecture 6: Stacks 2018 15 / 29
Stack Applications Run Time Memory Management The run-time stack is used to manage allocation and deallocation of memory at run time. A frame is created to store a function s private information. The frame stores function arguments, the return address, and local variables for the function. A stack-pointer is also stored there. During execution, the code uses the variables in the frame at the top of the stack. Upon function return, the frame is deleted. The destructors for all objects declared in that frame will be executed. This automatic allocation and deallocation of memory is fundamental to implementing recursive functions. Alice E. Fischer Data Structures L5, Stacks... 16/29 Lecture 6: Stacks 2018 16 / 29
Stack Applications Recursion A frame is created at run time, when a function is called, to store its private information. A recursive function calls itself, possibly again and again. Each time, a new frame is laid on the stack. So there can be many frames on the run-time stack for the same function. When the function code refers to a variable, the instance in the newest frame is used. We say that the stack-top frame is the current context. When each call returns, the next-older frame becomes the current context. To understand recursion, you must understand how these frames are laid on the stack, and also how they are removed. Alice E. Fischer Data Structures L5, Stacks... 17/29 Lecture 6: Stacks 2018 17 / 29
Stack Applications Parsing Source Code Every modern compiler uses a stack to analyze the syntax of source code. Begin- and end- symbols must correspond. Expressions must be analyzed according to precedence and associativity. The scope of parameters and local variables must be defined properly. A variable name refers to the object defined in the local scope, if any. If not, it refers to a symbol defined in some enclosing block. A frame is the unit of data stored on the stack. A frame is created when a function is called and deleted when the function returns. Errors are identified when one of the stacks is empty when it should have something on it, or when there are operands on the operand stack when the last operator has been evaluated. Alice E. Fischer Data Structures L5, Stacks... 18/29 Lecture 6: Stacks 2018 18 / 29
Stack Application: Parsing Stack Application:Precedence Parsing The process of translating expressions in a program is based on the algorithm called precedence parse. Here, we describe a very simple precedence-parse algorithm for a language with only four operators and parentheses. Initially, the operands are numbers in a desktop calculator and either numbers or variable names in a compiler. As the process progresses, tree-nodes are created by combining two operands and an operator. Then a new node is pushed onto the operand stack in place of the two that were removed to build it. The operators are predefined, and every operator has a precedence. The inputs are read one at a time. Each one is classified as operand or operator. Whitespace is used to separate operators and operands from each other, but is otherwise ignored. Every expression must end in a newline. Alice E. Fischer Data Structures L5, Stacks... 19/29 Lecture 6: Stacks 2018 19 / 29
Stack Application: Parsing A Simple Expression Language These rules are similar to, but not quite like the rules in C. C has many operators with precedences from 1 to 17. We have only parentheses (, ) and four operators, all binary: +,,, /, Precedence values for these operators, as given in the C precedence table: + = 12, = 12, = 13, / = 13 Consider an expression with two operators such as a + b - c The operators +, - are surrounded by three operands, a, b, c If the precedence of the two operators is the same, the leftmost operator will grab the operand in the middle. Otherwise, the operator with higher precedence will grab it. Thus a + b - c becomes (a + b) - c but a + b * c becomes a + (b * c) Alice E. Fischer Data Structures L5, Stacks... 20/29 Lecture 6: Stacks 2018 20 / 29
Stack Application: Parsing The Precedence Parsing Algorithm Initialize the operator stack with a dummy element that has precedence 0. Read the next input symbol. If it is an operand, push it onto the operand stack. If a left-paren, (, push it onto the operator stack with precedence 0. If a right-paren, ), discard it and process all operators back to the matching (. Then pop the ( from the stack. Otherwise it is a binary operator. Compare its precedence to the precedence of the operator at the top of the stack. If the precedence of the new operator is greater than the stack-top operator, push the new operator onto the stack. Otherwise, do a reduce() operation. See the next slide. Quit if the stack-top operator is the dummy with precedence 0. Otherwise, repeat from the read operation. Alice E. Fischer Data Structures L5, Stacks... 21/29 Lecture 6: Stacks 2018 21 / 29
Stack Application: Parsing Reducing the Stack To reduce(): Pop an operator off the operator-stack. Pop two operands off the operand-stack. Make a new operand-node using the popped items. Be sure not to reverse the left and right sides. Push the new node onto the operand stack, Alice E. Fischer Data Structures L5, Stacks... 22/29 Lecture 6: Stacks 2018 22 / 29
Stack Application: Parsing Parsing a + b - c tree-stack: op-stack: a dum:0 b + :12 - :12 tree-stack: a b + c op-stack: dum:0 - :12 newline tree-stack: a b c op-stack: + - dum:0 Alice E. Fischer Data Structures L5, Stacks... 23/29 Lecture 6: Stacks 2018 23 / 29
Stack Application: Parsing Parsing a + b * c tree-stack: a b op-stack: dum:0 + :12 tree-stack: a b c newline op-stack: dum:0 + :12 * :13 tree-stack: a + b * c op-stack: dum:0 Alice E. Fischer Data Structures L5, Stacks... 24/29 Lecture 6: Stacks 2018 24 / 29
Stack Application: Parsing Parsing y * ( x - 2 ) tree-stack: op-stack: y dum:0 x 2 *:13 ( -:12 ) tree-stack: y x 2 - op-stack: dum:0 *:13 newline tree-stack: y x 2 op-stack: * - dum:0 Alice E. Fischer Data Structures L5, Stacks... 25/29 Lecture 6: Stacks 2018 25 / 29
Infix, Postfix, and Prefix Expressions Notations for expressions. In mathematics and in most programming languages, infix notation is used to write expressions. However, there are languages that use other notations. C, Java, Python, C# all use infix, with parentheses to modify the defaults. Scheme, Lisp, and Haskell all use prefix notation. The operator is written first, then its operands. Function-call parentheses are used in Lisp and Scheme but not in Haskell. Forth and Postscript both use postfix notation. Parentheses are unnecessary for grouping and not used. All three can be parsed into expression trees that mean the same thing. However, they do look different. Alice E. Fischer Data Structures L5, Stacks... 26/29 Lecture 6: Stacks 2018 26 / 29
Infix, Postfix, and Prefix Expressions Infix Notation We discussed the precedence parse algorithm in the prior section. It starts with a text expression and outputs an expression tree. 21 y 5 x 2 3 5-2 7 3 * 21 = 21 y = ( x - 2 ) * 3 ; Infix: each operand is between its operators. Parentheses are often required in this notation. Alice E. Fischer Data Structures L5, Stacks... 27/29 Lecture 6: Stacks 2018 27 / 29
Infix, Postfix, and Prefix Expressions Prefix Notation Prefix notation relies only on position, not on precedence, associativity, and parentheses. A tree-stack and the op-stack are used, but reduce is called any time an operation is read and two operands exist on the tree-stack. 21 = 21 5 y * - x 2 7 5 2 3 21 Prefix: The operator is written before its two operands. Alice E. Fischer Data Structures L5, Stacks... 28/29 Lecture 6: Stacks 2018 28 / 29
Infix, Postfix, and Prefix Expressions Postfix Notation Postfix notation relies only on position, not on precedence, associativity, and parentheses. Only one stack, the tree-stack, is needed. Reduce is called any time an operator is read as input. 21 5 y x 2-7 * = 5 2 3 7 21 Postfix: The operator is written after its two operands. Alice E. Fischer Data Structures L5, Stacks... 29/29 Lecture 6: Stacks 2018 29 / 29