Vous êtes sur la page 1sur 4

Dynamic Storage Management CSCI 3320/8325 Data Structures

Module 5 Lists, Stacks, and Queues (Part 3) Some programming languages do not provide mechanisms for the dynamic allocation and deallocation of persistent storage (that is, storage for which allocation and deallocation is not directly related to procedure or function entry and exit). Also, in some systems, the overhead required to use dynamic storage allocation is excessively expensive (in terms of time). In these cases, an alternate method for implementing dynamic storage management is useful. This is sometimes called the cursor method.
2

Cursor Implementation of Linked Lists


The basic requirements for the typical linked list implementations are as follows:
Data is stored in nodes, each of which also contains a pointer to the next node (and perhaps the previous node). Nodes can be obtained from or returned to a systemprovided storage pool using operators like new and delete.

The Storage Pool


In C and C++, the storage pool is managed by a set of library functions provided by the language.
At the beginning of execution, a suitably large pool of storage is obtained from the operating system. When a program requests a new node, storage is obtained from the pool by a language library function. If insufficient storage is available in the pool, the library requests additional pool space from the operating system. When storage is released by a program, a language library function returns it to the storage pool.

In the cursor implementation, we build the storage pool ourselves, storing our unused nodes in a linked list stored in an array.
3

The cursor implementation will normally obtain a fixed amount of storage from the system as an array, and provide functions similar to new and delete for an application program to use.
4

Cursor Implementation Basics


Initially, an array of nodes is allocated (perhaps statically) and initialized as a list for the storage pool.
Pointers are typically not used; instead, subscripts of array elements are used to identify nodes. Each node (array element) contains an integer pointing to the next node; the last node contains zero, which is interpreted as the end of the list. Either an integer pointing to the first node or a header node is used to identify the beginning of the list.

The Stack ADT


A stack is a list that provides two fundamental operations:
Push insert an element at the top of the stack Pop remove and return the element at the top of the stack (an error is reported if the stack is empty)

When the application requests a new node, one is removed from the head of the storage pool list. If the list is empty, zero is returned (indicating allocation failure). Unneeded nodes are added back to the list, typically at the front (for speed).
5

Note that operations are only permitted at the top of the stack, which is equivalent to one end of the list. Stacks are frequently called LIFO (last in, first out), since the last item pushed on a stack will be the first item removed by a pop operations. It is sometimes useful to provide an additional operation to determine if a stack is empty.
6

Stack Implementations
Stacks can easily be implemented using linked lists:
Push and pop operations are equivalent to insert and retrieve/delete operations on the node in the first position. Testing for an empty stack is equivalent to testing for an empty list.

Stack Application: Checking Balanced Symbols


In most strings that contain grouping symbols (e.g. parentheses, brackets, and braces), it is expected that these symbols be balanced appropriately. For example:
( ( [ { }( )] ) ) is balanced correctly, but ( ( [ ( { ) } ] ) ) is not, even though it has the correct number of left and right symbols.

Stacks can also easily be constructed using a statically allocated array, with an integer variable (e.g. top) indicating the subscript of the first unused location beyond the top of the stack. The basic operations are simple:
Push: stack[top++] = element; Pop: element = stack[--top]; isEmpty: top == 0

Tests for an empty stack prior to popping, and for a full stack prior to push (in an array implementation) are highly recommended.
7

The algorithm for checking a string for symbol balance is simple, and only requires the use of a single stack of characters.
8

Algorithm: Checking Balanced Symbols


Process the characters in the string from left to right. For each character:
If the character is a left, or opening, grouping symbol (, [, or { then push it on the stack. If the character is a right grouping symbol:
If the stack is empty, report an error Pop the stack; if the symbol removed from the stack is not the left grouping symbol corresponding to the right grouping symbol in the input, report an error.

Stack Application: Evaluating Postfix Expressions


The way in which arithmetic expressions are normally written is called infix notation. This is because the operators appear between the operands. Computers (and some calculators, like some manufactured by HP), use postfix notation to represent expressions. In these expressions, operators appear after the operands to which they apply. Postfix is sometimes called reverse Polish notation. For example, the infix expression (1+2)(3+4) is equivalent to the postfix expression 1 2 + 3 4 + . Note that parentheses are unnecessary in postfix notation. Parentheses are required in infix notation to guarantee the correct order of evaluation. Evaluating an expression given in postfix notation requires only a single stack of numbers (of the appropriate type).
10

Once all characters in the string have been processed, the stack should be empty. If it is not empty, report an error.

Algorithm: Evaluating Postfix Expressions

Process the elements (numeric values and operators) in the postfix expression from left to right.
When a numeric value is encountered, push it on the stack. When an operator is encountered, pop two values from the stack, apply the operator, and push the resulting value. If at least two values are not on the stack, an error is reported.

Example: Evaluating Postfix Expressions


Expression
12+34+ 12+34+ 12+34+ 12+34+ 12+34+ 12+34+ 12+34+

Action Push Push Pop 2 & 1, add, push Push Push Pop 4 & 3, add, push Pop 7 & 3, multiply, push 1

Stack

21 3 33 433 73 21
12

When all expression elements have been processed, exactly one value should remain on the stack, and this is the result of the expressions evaluation. We have assumed binary (two-operand) operators here, but postfix notation can also accommodate unary (oneoperand) and operators with more than two operands.
11

Stack Application: Infix to Postfix Conversion


A slightly more complicated application of stacks is the conversion of infix expressions to postfix. If we ignore the possibility of errors, and consider only binary operators (with the usual priorities) and parentheses, the conversion requires only a single stack. The basic idea is to scan the infix expression, left to right, emitting operands as they are encountered, and pushing operators and left parentheses on the stack, popping them off when an operator of lower priority or the end of the expression is encountered. + and are considered low priority; and / are high priority.
13

Algorithm: Infix to Postfix Conversion


Scan the infix expression from left to right. For each symbol:
If the symbol is an operand, output it immediately. If the symbol is a left parenthesis, push it on the stack. If the symbol is a right parenthesis, continually pop the stack and output the operator until the corresponding left parenthesis is popped. Otherwise, pop and output operators from the stack as long as they have a priority higher than or equal to the current operator (but never pop a left parenthesis).

At the end of the expression, pop and output operators until the stack is empty.
14

Example: Infix to Postfix Conversion


Expression (1+2)(3+4) (1+2)(3+4) (1+2)(3+4) (1+2)(3+4) (1+2)(3+4) (1+2)(3+4) (1+2)(3+4) (1+2)(3+4) (1+2)(3+4) (1+2)(3+4) (1+2)(3+4) (1+2)(3+4)
end

Action Push Output Push Output Pop, output, pop Push Push Output Push Output Pop, output, pop Pop, output

Stack (

Output 1

Stack Application: Handling Function Calls


Most modern programming languages (and most modern processors) use a stack to record procedure or function invocations.
When a function call is made, the return address and parameters (values or pointers) are pushed on the stack as a group, usually called an activation record or stack frame. The called function allocates space on the stack for its local variables (with the auto storage class in C/C++). When the function returns, it pops (and discards) its local storage and parameters, then pops the return address and returns to the calling program at the appropriate location.

+( +( ( ( +( +(

1 12 12+ 12+ 12+ 12+3 12+3 12+34 12+34+ 12+34+


15

16

Stacks and Tail Recursion


Recursive procedures and functions are easily implemented by compilers using the stack to record function invocation information. Unfortunately, poor use of recursion can result in the use of excessive stack space. If the last statement in a function is a recursive call to itself (called tail recursion), the function can be rewritten as a while loop including the base case(s), the remaining body of the function, and assignment of each of the recursive calls parameters to the appropriate local variables.

The Queue ADT


Queues are lists with several restricted operations, similar to stacks. The usual operations permitted on a queue are:
enqueue add an element to the rear of the queue (one end of the list) dequeue remove an element from the front of the queue (the other end of the list) isEmpty an optional operation that returns true when the queue is empty

As expected, the running times for these operations are all O (1) for linked list or array implementations.
18

17

Array Implementation (1)


An array implementation of a queue usually requires an array of elements (Q) and three related integers:
size the number of queued entries front subscript of element at the front of the queue rear subscript of element at the rear of the queue

Array Implementation (2)


The basic operations are easily implemented:
Enqueue q[rear++] = element; size++; Dequeue element = q[front++]; size--; isEmpty (size == 0)

Initialization of the queue is performed by setting size, front, and rear to 0. An additional operation, isFull, may be used to determine if the space available for the queue has been exhausted.

With continual addition of elements to one end and removal of elements from the other end, cause the region of the array occupied by elements will shift, so eventually the value of rear becomes equal to the size of the array, even though size may be small. One solution to this problem is to continually move the elements down in the array, so the element at the front of the queue has the subscript zero. Unfortunately, this is a poor solution, since the running time now becomes O (N ).
20

19

Circular Array Implementation


The solution to this problem is to allow the region of the array occupied by the queue to wrap around the end of the array. This is accomplished with the following changes; MAX is the size of the array:
Enqueue q[rear] = element; size++; rear = (rear + 1) % MAX; Dequeue element = q[front]; size--; front = (front + 1) % MAX; isEmpty (size == 0)

Queue Applications (1)


Queues are sometimes called FIFO structures first in, first out. Queues are used in numerous applications, in particular system-related software. Some examples include:
Operating systems
Processes (jobs) waiting to be executed are placed in a queue and executed in the order they arrived (first in, first out). Print jobs are arranged in a queue so they are printed in a logical order; there may be multiple queues, one per printer. Users may type data before a program needs it; the extra keystrokes are queued for delivery to the program when requested.

Each of these operations still has a running time of O (1).


21

Networks individual messages arrive at a routing point from multiple sources, and need to be queued for retransmission to another destination if the channel to that destination is being used.
22

Queue Applications (2)


Queues are also used in application software in the same way they are used in real life. In businesses where there are multiple customers and multiple servers (people or machines that process customers), the customers form a queue. When a server finishes processing a customer, the next customer is taken from the head of the queue. Queuing theory, a specialized branch of mathematics, is the study queuing systems, using parameters like the length of time a customer has to wait in line, the average queue length, and so forth.

23

Vous aimerez peut-être aussi