












Study with the several resources on Docsity
Earn points by helping other students or get them with a premium plan
Prepare for your exams
Study with the several resources on Docsity
Earn points to download
Earn points by helping other students or get them with a premium plan
Material Type: Paper; Class: DATA STRUCTURES; Subject: Computer Science; University: Oregon State University; Term: Winter 2009;
Typology: Papers
1 / 20
This page cannot be seen from the preview
Don't miss anything!













You are familiar with the concept of a stack from many everyday examples. For example, you have seen a stack of books on a desk, or a stack of plates in a cafeteria. The common characteristic of these examples is that among the items in the collection, the easiest element to access is the topmost value. In the stack of plates, for instance, the first available plate is the topmost one. In a true stack abstraction that is the only item you are allowed to access. Furthermore, stack operations obey the last-in, first-out principle, or LIFO. If you add a new plate to the stack, the previous topmost plate is now inaccessible. It is only after the newly added plate is removed that the previous top of the stack once more becomes available. If you remove all the items from a stack you will access them in reverse chronological order – the first item you remove will be the item placed on the stack most recently, and the last item will be the value that has been held in the stack for the longest period of time. Stacks are used in many different types of computer applications. One example you have probably seen is in a web browser. Almost all web browsers have Back and Forward buttons that allow the user to move backwards and forwards through a series of web pages. The Back button returns the browser to the previous web page. Click the back button once more, and you return to the page before that, and so on. This works because the browser is maintaining a stack containing links to web pages. Each time you click the back button it removes one link from this stack and displays the indicated page.
Suppose we wish to characterize the stack metaphor as an abstract data type. The classic definition includes the following four operations: Push (newEntry) Place a new element into the collection. The value provided becomes the new topmost item in the collection. Usually there is no output associated with this operation. Pop () Remove the topmost item from the stack. Top () Returns, but does not remove, the topmost item from the stack. isEmpty () Determines whether the stack is empty Note that the names of the operations do not specify the most important characteristic of a stack, namely the LIFO property that links how elements are added and removed. Furthermore, the names can be changed without destroying the stack-edness of an abstraction. For example, a programmer might choose to use the names add or insert rather than push, or use the names peek or inspect rather than top. Other variations are also common. For example, some implementations of the stack concept combine the pop and top operations by having the pop method return the value that has been removed from the stack. Other implementations keep these two tasks separate, so that the only access to the topmost element is through the function named top. As long as the
fundamental LIFO behavior is retained, all these variations can still legitimately be termed a stack. Finally, there is the question of what to do if a user attempts to apply the stack operations incorrectly. For example, what should be the result if the user tries to pop a value from an empty stack? Any useful implementation must provide some well-defined behavior in this situation. The most common implementation technique is to throw an exception or an assertion error when this occurs, which is what we will assume. However, some designers choose to return a special value, such as null. Again, this design decision is a secondary issue in the development of the stack abstraction, and whichever design choice is used will not change whether or not the collection is considered to be a stack, as long as the essential LIFO property of the collection is preserved. The following table illustrates stack operations in several common languages: Java class Stack C++ stack adapter Python list push push(value) push(value) lst.append(value) pop pop() pop Del lst[-1] top peek() top() lst[-1] isEmpty empty() empty() len(lst) == 0 In a pure stack abstraction the only access is to the topmost element. An item stored deeper in the stack can only be obtained by repeatedly removing the topmost element until the value in question rises to the top. But as we will see in the discussion of implementation alternatives, often a stack is combined with other abstractions, such as a dynamic array. In this situation the data structure allows other operations, such as a search or direct access to elements. Whether or not this is a good design decision is a topic explored in one of the lessons described later in this chapter. To illustrate the workings of a stack, consider the following sequence of operations: push(“abe”) push(“amy”) push(“andy”) pop() push(“anne”) push(“alfred”) pop() pop() The following diagram illustrates the state of the stack after each of the eight operations.
Question : Web browsers often provide a history feature, which records all web pages accessed in the recent past. How is this different from the back stack? Describe how the history should change when each of the three following conditions occurs: (a) when the user moves to a new page by pressing a hyperlink, (b) when the user restores an old page by pressing the back button, and (c) when the user moves forward by pressing the forward button.
An operating system uses a stack in order to correctly process backspace keys in lines of input typed at a keyboard. Imagine that you enter several keys and then discover a mistake. You press the backspace key to move backward over a previously entered character. Several backspaces may be used in turn to erase more than one character. If we use < to represent the backspace character, imagine that you typed the following: correcktw<<<<t tw<ext The operating system function that is handling character input will arrive at the correct text because it stores the characters as they are read in a stack-like fashion. Each non-backspace character is simply pushed on the stack. When a backspace is typed, the topmost character is popped from the stack and erased. Question : What should be the effect if the user enters a backspace key and there are no characters in the input?
Another example of a stack that we discussed briefly in an earlier chapter is the activation record stack. This term describes the spaced used by a running program to store parameters and local variables. Each time a function or method is invoked, space is set aside for these values. This space is termed an activation record. For example, suppose we execute the following function void a (int x) int y y = x - 23; y = b (y)
When the function a is invoked the activation record looks something like the following: Imagine that b has the following recursive definition int b (int p) if (p < 15) return 1; else return 1 + b(p-1) Each time the function b is invoked a new activation record is created. New local variables and parameters are stored in this record. Thus there may be many copies of a local variable stored in the stack, one for each current activation of the recursive procedure. Functions, whether recursive or not, have a very simple execution sequence. If function a calls function b, the execution of function a is suspended while function b is active. Function b must return before function a can resume. If function b calls another function, say c, then this same pattern will follow. Thus, function calls work in a strict stack-like fashion. This makes the operation of the activation record stack particularly easy. Each time a function is called new area is created on the activation record stack. Each time a function returns the space on the activation record stack is popped, and the recovered space can be reused in the next function call. Question : What should (or what does) happen if there is no available space in memory for a new activation record? What condition does this most likely represent?
A simple application that will illustrate the use of the stack operations is a program to check for balanced parenthesis and brackets. By balanced we mean that every open parenthesis is matched with a corresponding close parenthesis, and parenthesis are properly nested. We will make the problem slightly more interesting by considering both parenthesis and brackets. All other characters are simply ignored. So, for example, the inputs (x(y)(z)) and a( {(b)}c) are balanced, while the inputs w)(x) and p({(q)r)} are not.
associativity rules when desired. For example, we could explicitly have written 6 – (3 – 2). The evaluation of infix expressions is not always easy, and so an alternative notion, termed postfix notation , is sometimes employed. In postfix notation the operator is written after the operands. The following are some examples: Infix 2 + 3 2 + 3 * 4 (2 + 3) * 4 2 + 3 + 4 2 - (3 – 4) Postfix 2 3 + 2 3 4 * + 2 3 + 4 * 2 3 + 4 + 2 3 4 - - Notice that the need for parenthesis in the postfix form is avoided, as are any rules for precedence and associativity. We can divide the task of evaluating infix expressions into two separate steps, each of which makes use of a stack. These steps are the conversion of an infix expression into postfix, and the evaluation of a postfix expression.
To convert an infix expression into postfix we scan the value from left to right and divide the tokens into four categories. This is similar to the balanced parenthesis example. The categories are left and right parenthesis, operands (such as numbers or names) and operators. The actions for three of these four categories is simple: Left parenthesis Push on to stack Operand Write to output Right parenthesis Pop stack until corresponding left parenthesis is found. If stack becomes empty, report error. Otherwise write each operator to output as it is popped from stack The action for an operator is more complex. If the stack is empty or the current top of stack is a left parenthesis, then the operator is simply pushed on the stack. If neither of these conditions is true then we know that the top of stack is an operator. The precedence of the current operator is compared to the top of the stack. If the operator on the stack has higher precedence, then it is removed from the stack and written to the output, and the current operator is pushed on the stack. If the precedence of the operator on the stack is lower than the current operator, then the current operator is simply pushed on the stack. If they have the same precedence then if the operator associates left to right the actions are as in the higher precedence case, and if association is right to left the actions are as in the lower precedence case. The following diagram illustrates the state of the stack and the output as different characters in the input are read: picture
Question : Using this algorithm, show the state of the stack and the output for each of the following expressions: example
The advantage of postfix notation is that there are no rules for operator precedence and no parenthesis. This makes evaluating postfix expressions particularly easy. As before, the postfix expression is evaluated left to right. Operands (such as numbers) are pushed on the stack. As each operator is encountered the top two elements on the stack are removed, the operation is performed, and the result is pushed back on the stack. Once all the input has been scanned the final result is left sitting in the stack. The following illustrates the state of the stack during the evaluation of the expression picture Question : Using this algorithm, show the state of the stack and the output for each of the following expressions. Question : What error conditions can arise if the input is not a correctly formed postfix expression? What happens for the expression 3 4 + +? How about 3 4 + 4 5 +?
In the worksheets we will discuss two of the major techniques that are typically used to create stacks. These are the use of a dynamic array, and the use of a linked list. Study questions that accompany each worksheet help you explore some of the design tradeoffs a programmer must consider in evaluating each choice. The self-study questions given at the end of this chapter are intended to help you measure your own understanding of the material. Exercises and programming assignments that follow the self-study questions will explore the concept in more detail. Worksheet 16 Introduction to the Dynamic Array Worksheet 17 Dynamic Array Stack Worksheet 18 Introduction to Linked List, Linked List Stack
An array is a simple way to store a collection of values:
be handled. The first occurs when an attempt is made to remove a value from an empty stack. In this situation you should throw a StackUnderflow exception. The second exceptional condition is more difficult. When a push instruction is requested but the size is equal to the capacity, there is no space for the new element. In this case a new array must be created. Typically, the size of the new array is twice the size of the current. Once the new array is created, the values are copied from existing array to the new array, and the new array replaces the current array. Since there is now enough room for the new element, it can be inserted. Worksheet 16 explores the implementation of a dynamic array stack. In the exercises at the end of the chapter you will explore the idea that while the worst case execution time for push is relatively slow, the worst case occurs relatively infrequently. Hence, the expectation is that in the average execution of push will be quite fast. We describe this situation by saying that the method push has constant amortized execution time.
An alternative implementation approach is to use a linked list. Here, the container abstraction maintains a reference to a collection of elements of type Link. Each Link maintains two data fields, a value and a reference to another link. The last link in the sequence stores a null value in its link. The advantage of the linked list is that the collection can grow as large as necessary, and each new addition to the chain of links requires only a constant amount of work. Because there are no big blocks of memory, it is never necessary to copy an entire block from place to place. Worksheet 17 will introduce the idea of a linked list, and explore how a linked list can be used to implement a stack.
Memory Management The linked list and the Dynamic Array data structures take different approaches to the problem of memory management. The Dynamic Array uses a large block of memory. This means that memory allocation is much less common, but when it occurs much more work must be performed. The linked list allocates a new link every time a new element is added. This makes memory allocation more frequent, but as the memory blocks are small less work is performed on each allocation. If, as is often the case, a linked list is used to store pointers to a dynamically allocated value, then there are two dynamically allocated spaces to manage, the link and the data field itself: An important principle of good memory management is “everybody must clean up their own mess”. (This is sometimes termed the kindergarten principle ). The linked list allocates space for the link, and so must ensure that the space is freed by calling the associated pop routine. The user of the list allocates space for the data value, and must therefore ensure that the field is freed when no longer needed. Whenever you create a dynamically allocated value you need to think about how and when it will be freed. Earlier we pointed out the problem involved in placing a new element into the middle of a Dynamic Array (namely, that the following elements must then be moved). Linked lists will help solve this problem, although we have not yet demonstrated that in this chapter. For the Dynamic Array we created a single general-purpose data structure, and then showed how to use that data structure in a variety of ways. In examining the linked list we will take a different approach. Rather than making a single data abstraction, we will examine the idea of the linked list in a variety of different forms. In subsequent lessons we will examine a number of variations on this idea, such as header or sentinel links, single versus double links, maintaining a pointer to the last as well as the first link, and more. Occasionally you will find links placed directly into a data object. For example, suppose you were creating a card game, and needed a list of cards. One way to do this would be the following: Each card can then be used as a link in a linked list. link data value
return for their cars, how many spaces must he leave empty to ensure that he can reach any possible car?
each simple insertion, and n for each time an array of n elements is copied. Then print out a table showing 200 consequitive insertions into a stack, and the value of the unit cost at each step.
for your stack can be very simple, since you can simply invoke the functions in the parent class.
you find the link to remove, you simply update the predecessor link. Implement the three bag operations using this approach.
The wikipedia entry for “Stack (data structure)” provides another good introduction to the concept. Other informative wikipedia entries include LIFO, call stack, and stack-based memory allocation. Wikipedia also provides a bibliographical sketch of Friedrich L. Bauer, the computer scientist who first proposed the use of stack for evaluating arithmetic expressions. The NIST Dictionary of Algorithms and Data Structures also has a simple description of the stack data type. (http://www.nist.gov/dads/HTML/stack.html) If you google “Stack in Java” (or C++, or C, or any other language) you will find many good examples.