Professional Documents
Culture Documents
Stacks
LIFO structure (Last-In-First-Out)
Implementation
Array-Based
Also must add checks for full stack during push and empty stack during pop.
Infix is the most common for pencil-and-paper work. Prefix and postfix are common in technical applications
(some programming languages such as Lisp/Scheme use prefix, some older handheld calculators like some HP
calculators use postfix). Postfix notation does not need parentheses, since the evaluation scheme determines
precedence of operations by the operation’s position in the expression
Algorithm to directly evaluate an infix expression is complex. Instead, it’s often much easier to perform the
following two steps:
• Convert or translate an infix expression into a postfix expression
• Evaluate the postfix notation expression
Each step is a separate algorithm, and each algorithm is implemented using a stack.
Algorithm for Infix-To-Postfix Translation (aka “Shunting Yard Algorithm” by Edsgar Dijkstra)
Assume S contains 4 token types to define expressions (simplified to exclude function/method calls):
LP (left paren)
RP (right paren)
NUM (number)
OP (operation + - * /)
Note
• Numbers or operands are never placed on the stack, they move from input string directly to result string.
• Left parens appear on the stack temporarily, but right parens are never placed on the stack, and the final
result expression contains no parens at all.
• Handling of operators is the most complex case. Operators move from infix string, to stack, to result string,
but precedence rules require special care.
Infix To Postfix Examples
Example: “3*4+5”
Token Type Result String Stack
3 NUM 3
* OP 3 *
4 NUM 34 *
+ OP 34* +
5 NUM 34*5 +
Result String: “3 4 * 5 +”
Example: “3+4*5”
Token Type Result String Stack
3 NUM 3
+ OP 3 +
4 NUM 34 +
* OP 34 +*
5 NUM 345 +*
Result String: “3 4 5 + *”
Example: “((3/5)+(4/7))/(2*3+4*5)”
Example (2+3*4)*(3+4*2)
This algorithm is even simpler than the last one. Read the postfix expression from left to right. Whenever you read
a number or symbol (operand), push it onto the stack. Whenever you read an operation, pop the stack twice to get
the top two operands off the stack, apply the operator to these operands, and push the result back onto the stack.
When you reach the end of the postfix expression, the final result should be the single value on the stack. Pop the
stack to get the final value, leaving the stack in its initial empty state.
Result = Pop ST
Note
• This algorithm only evaluates input expressions that are already in postfix notation, it won’t work for infix
or prefix expressions.
• Operations are never placed onto the stack, only numbers (values).
• Since the input is in postfix notation, there are no parentheses to process at all.
Implementations
Array Based
Linear Queue
Easy to implement using a smart array, but wasteful of available space
Head and tail pointers “drift” to the right, array elements at indexes before head index are wasted.
Circular Queue
Very popular implementation, implemented like a linear queue, but the head and tail pointers are allowed to “wrap
around” back to the left end of the array if they “run off” the right end of the array.
Queue can be expanded if it runs out of space, just like a smart array
Problem is that it requires careful logical checking to maintain queue in consistent state.
Node-and-Link-Based
As with the stack, the queue can easily be implemented as a linked list
public void enqueue(int v) { insertAtTail(v); }
public int dequeue() { return removeFromHead(); }
Applications
Buffers
There are many applications for queues, for example, in operating systems and other systems programs to
implement buffers. A buffer is a temporary storage area in RAM used to interface between a producer of data and a
consumer of data.
For example, when a program in RAM reads from or writes to a file, the slow access speed of the hard drive makes
it impractical for a running program to access the disk directly. Instead, a buffer is used to sit between the
application running in RAM (the consumer of the data) and the data in a file on the disk (the producer of the data).
When the application opens the file and issues a read command for the first time, the operating system accesses the
disk and moves a relatively large chunk of data from disk to the buffer (more data than the program actually
requested). Whenever the application now issues a read command, the operating system just goes to the buffer in
RAM and fetches the next requested data. In the background, the operating system will quietly keep the buffer filled
by occasionally accessing the file on disk and moving more chunks of data from disk to RAM, asynchronously from
the application. When the application writes data to a file on disk, the same process happens in reverse. The
operating system quietly moves data from the application to the buffer, and then later moves large chunks of data
from the buffer to the disk. This is why it is especially important to close or “flush” the output buffer when writing
to a file. If your program ends without the close or flush, the last bytes of data written to the buffer may not get
copied to the file, and this data will be lost.
This is a mathematical model for how a queue works. It’s a widely studied mathematical model and is the simplest
model for how a queue works. The notation for the describing the behavior of the queue is defined by the notation:
• x/y/n
where
• x identifies the random distribution of customer interarrival time intervals
• y identifies the random distribution of service time intervals
• n identifies the number of servers for the queue
The identifier “M” stands for “Markovian” and is used to describe interarrival or service times that are distributed
exponentially. The exponential distribution has the form
For M/M/1 queues, it is customary to represent the interarrival distribution mean as 1/λ, and the service time mean
as 1/μ.
Note that the traffic intensity ρ must be < 1 for the M/M/1 queue
Worked Examples
Suppose the mean interarrival time to a queue is 3 minutes, and the mean service time is 1 minute.
λ = 1/3, μ = 1, ρ = 1/3
N = (1/3)/(2/3) = 1/2 customers
T = 1/(2/3) = 2 minutes
Now suppose the mean interarrival time is 3 minutes, and the mean service time is 2.7 minutes
λ = 1/3 = 0.33, μ = 1/2.7 = 0.37, ρ = 0.33/0.37 = 0.9
N = 0.9/0.1 = 9 customers
T = 1/0.037 = 27 minutes
Now suppose the average interarrival time is 3 minutes, and the average service time is 2.9 minutes
λ = 1/3 = 0.33, μ = 1/2.9 = 0.3448, ρ = 0.33/0.3448 = 0.9667
N = 0.9667/0.033 = 29 customers
T = 1/0.037 = 87 min
Simulation
For simple queueing problems, common performance measures can be calculated directly from known formulas.
If the problem becomes more complex, the simple formulas no longer apply and the system must be simulated by
writing a program that creates data structures to represent the queues, simple objects or tokens to represent
customers, and random numbers to represent interarrival and service times. The simulation measures the size
(length) of the queues and other performance measures at regular intervals and reports means or distributions at the
end of the simulation.
Example Traces
New A A []
A push 3 A [3]
A push 4 A [4 3]
A push 5 A [5 4 3]
A push 6 A [6 5 4 3]
New B A [6 5 4 3]
B []
B push(pop A) A [5 4 3]
B [6]
B push(pop A) A [4 3]
B [5 6]
B push(pop A) A [3]
B [4 5 6]
B push(pop A) A []
B [3 4 5 6]