03-Stacks Queues
03-Stacks Queues
und Algorithmen
h tt p : / / a l g s 4 . c s . p r i n c e t o n . e d u
2
Arrays & Linked Lists
None
3
Arrays
• Length xed at time of allocation
• Supported as Python type (module array)
• Data is stored densely in memory
• No overhead per element
myArray = DSA.intArray(16)
4
fi
Linked Lists
• Length depends on elements in the list
• Supported as Python collection
(e.g. collections.deque, more later)
• Data is stored as a pair of data and
reference(s)
• Reference overhead per element
None
5
Singly Linked Lists
None
class ListElement:
def __init__(self, item):
self.item = item
self.next = None
6
Doubly Linked Lists
None None
class ListElement:
def __init__(self, item):
self.item = item
self.prev = None
self.next = None
7
Stacks and Queues
8
Which item do we remove?
9
fi
Which item do we remove?
9
fi
fi
fi
Separate interface and implementation!
• Client can't know details of implementation ⇒
client has many implementation from which to choose.
10
Separate interface and implementation!
• Client can't know details of implementation ⇒
client has many implementation from which to choose.
• Implementation can't know details of client needs ⇒
many clients can re-use the same implementation.
10
Separate interface and implementation!
• Client can't know details of implementation ⇒
client has many implementation from which to choose.
• Implementation can't know details of client needs ⇒
many clients can re-use the same implementation.
• Design: creates modular, reusable libraries.
10
Separate interface and implementation!
• Client can't know details of implementation ⇒
client has many implementation from which to choose.
• Implementation can't know details of client needs ⇒
many clients can re-use the same implementation.
• Design: creates modular, reusable libraries.
• Performance: use optimized implementation where it matters.
10
Stacks
11
Stack API
push pop
class Stack
12
Sample client
push pop
stack = Stack()
while not stdIsEmpty():
stack.push(stdReadString())
while not stack.isEmpty():
print(stack.pop(), end=" ")
print()
% more tinyTale.txt
it was the best of times ...
13
Question: How to implement a stack
with a linked list?
top of stack
top of stack
C
of best the was it None
14
fi
Answer ”C“
• Maintain pointer first to rst node in a singly-linked list.
• Push new item before rst.
• Pop item from rst.
top of stack
first
15
fi
fi
fi
Pop
save item to return
item = self.first.item
self.first = self.first.next
inner class
return item
16
fi
Push
save link to the list
oldfirst = self.first
oldfirst
first or
be
to
None
self.first.next = oldfirst
first not
or
be
to
None
17
class Stack:
class Node:
def __init__(self, item):
self.item = item
self.next = None
def __init__(self):
self.first = None
def push(self,e):
oldfirst = self.first
self.first = self.Node(e)
self.first.next = oldfirst
def pop(self):
item = self.first.item
self.first = self.first.next
return item
def isEmpty(self):
return self.first == None
18
Stack: linked-list implementation
performance
def push(self,e):
oldfirst = self.first
self.first = self.Node(e)
self.first.next = oldfirst Proposition:
def pop(self):
item = self.first.item
Every operation takes
self.first = self.first.next
return item
constant time in the
def isEmpty(self):
worst case.
return self.first == None
19
How to implement a stack with an
array?
A Can't be done ef ciently with an array!
top of stack
B
it was the best of times None None None None
0 1 2 3 4 5 6 7 8 9
top of stack
0 1 2 3 4 5 6 7 8 9
20
fi
Answer ”B“
• Use array s[] to store N items on stack.
• push(): add new item at s[N].
• pop(): remove item from s[N-1].
top of stack
0 1 2 3 4 5 6 7 8 9
N capacity = 10
def pop(self):
self.N-=1
return self.data[self.N]
22
Stack considerations
• Over ow and under ow.
• Under ow: throw exception if pop from an empty stack.
• Over ow: use resizing array for array implementation.
[stay tuned]
23
fl
fl
fl
fl
Stack considerations
• Over ow and under ow.
• Under ow: throw exception if pop from an empty stack.
• Over ow: use resizing array for array implementation.
[stay tuned]
• "None"-items. We allow "None"-items to be inserted.
23
fl
fl
fl
fl
Stack considerations
Loitering: Holding a reference to an object
when it is no longer needed.
24
Stack considerations
Loitering: Holding a reference to an object
when it is no longer needed.
def pop(self):
self.N-=1
return self.data[self.N]
loitering
24
Stack considerations
Loitering: Holding a reference to an object
when it is no longer needed.
def pop(self):
def pop(self):
self.N-=1
self.N-=1
item = self.data[self.N]
return self.data[self.N]
self.data[self.N] = None
return item
loitering
this version avoids "loitering":
python can reclaim memory for an object
only if no outstanding references
24
Resizing-array implementation
25
”Resize by 1“
• Start with a small class Stack:
def __init__(self):
array self.data = objArray(1)
self.N = 0
• push(): increase
def __resize(self, capacity):
size of array copy = objArray(capacity)
for i in range(self.N):
data[] by 1. copy[i] = self.data[i]
self.data = copy
• pop(): decrease def push(self,item):
size of array if self.N == len(self.data):
self.__resize(len(self.data)+1)
data[] by 1. self.data[self.N] = item
self.N+=1
26
Too expensive!
= N + (2 + 4 + … + 2(N – 1)) ~ N 2
27
fi
Q. How to grow array more ef ciently?
28
fi
Q. How to grow array more ef ciently?
A. If array is full, create a new array of twice the size, and copy items.
class Stack:
def __init__(self):
self.data = objArray(2)
self.N = 0
def push(self,item):
if self.N == len(self.data): "repeated doubling"
self.__resize(len(self.data)*2)
self.data[self.N] = item
self.N+=1
28
fi
class Stack:
def __init__(self):
self.data = objArray(2)
self.N = 0
def push(self,item):
if self.N == len(self.data): "repeated doubling"
self.__resize(len(self.data)*2)
self.data[self.N] = item
self.N+=1
29
fi
Array accesses to insert rst N = 2i items. N + (2 + 4 + 8 + … + N) ~ 3N.
128
64
red dots give cumulative average 3
0
0 number of push() operations 128
Amortized cost of adding to a Stack
Amortized analysis. Starting from an empty data structure, average running
time per operation over a worst-case sequence of operations.
30
fi
Q. How to shrink array?
31
Q. How to shrink array?
31
Too expensive!
N=4 to be or not
N=4 to be or not
32
Ef cient solution?
33
fi
Ef cient solution?
def pop(self):
self.N-=1
item = self.data[self.N]
if self.N > 0 and self.N == len(self.data)//4:
self.__resize(len(self.data)//2)
return item
33
fi
Performance
best worst
construct 1 1
push 1 N
pop 1 N
doubling and
size 1 1 halving operations
34
Performance
best worst amortized
construct 1 1 1
push 1 N 1
pop 1 N 1
doubling and
size 1 1 1 halving operations
34
Memory Usage
• Proposition. Uses between ~ ⋅N and ~ 4 ⋅N bytes
overhead to represent a stack with N items.
• ~ ⋅N when full.
35
𝝎
𝝎
𝝎
𝝎
𝝎
Resizing Array vs. Linked List
36
Resizing Array vs. Linked List
• Linked-list implementation.
• Every operation takes constant time in the worst case.
• Uses extra time and space to deal with the links.
first not
or
be
to
None
36
Resizing Array vs. Linked List
• Linked-list implementation.
• Every operation takes constant time in the worst case.
• Uses extra time and space to deal with the links.
first not
or
be
to
None
• Resizing-array implementation.
• Every operation takes constant amortized time.
• Less wasted space.
N=4 to be or not None None None None
36
Queues
37
Queue API
enqueue
class Queue
dequeue
38
Question: How to implement a
Queue with a linked list?
B
times of best the was it None
C
it was the best of times None
39
fi
”C“
• Maintain one pointer rst to rst node in a singly-linked list.
rst last
40
fi
fi
fi
fi
Dequeue
save item to return
item = self.first.item
self.first = self.first.next
inner class
return item
oldlast
last
first to
be
or
None
inner class
create a new node for the end
class Node: self.last = self.Node("not")
def __init__(self, item):
self.item = item oldlast
self.next = None first to
last
be
or
None not
None
oldlast.next = self.last
oldlast
last
first to
be
or
not
None
def __init__(self):
self.first = None
self.last = None
def enqueue(self,e):
oldlast = self.last
self.last = self.Node(e)
if self.isEmpty():
self.first = self.last
else:
oldlast.next = self.last
def dequeue(self):
item = self.first.item
special cases for
self.first = self.first.next
if self.isEmpty(): empty queue
self.last = None
return item
def isEmpty(self):
return self.first == None
43
Question: How to implement a Queue with an
array?
B
it was the best of times None None None None
0 1 2 3 4 5 6 7 8 9
C
times of best the was it None None None None
0 1 2 3 4 5 6 7 8 9
44
fi
Answer ”B“ & ”C“
• enqueue(): add new item at data[tail].
q[] None None the best of times None None None None
0 1 2 3 4 5 6 7 8 9
45
Applications
46
Stack applications
• Parsing in a compiler.
• Java virtual machine.
• Undo, e.g., in a word processor.
• Back button in a Web browser.
• PostScript language for printers.
• Implementing function calls in a compiler ...
47
Function calls
48
Recursion
• Recursive function. Function that calls itself.
• Note: You can always use an explicit stack to
remove recursion.
49
Recursion
• Recursive function. Function that calls itself.
• Note: You can always use an explicit stack to
remove recursion.
gcd (216, 192)
def gcd(p, q):
p = 216, q = 192 if q == 0: return p
else: return gcd(q, p % q)
49
Recursion
• Recursive function. Function that calls itself.
• Note: You can always use an explicit stack to
remove recursion.
gcd (216, 192)
def gcd(p, q):
p = 216, q = 192 if q == 0: return p
else: return gcd(q, p % q)
49
Recursion
• Recursive function. Function that calls itself.
• Note: You can always use an explicit stack to
remove recursion.
gcd (216, 192)
def gcd(p, q):
p = 216, q = 192 if q == 0: return p
else: return gcd(q, p % q)
gcd (24, 0)
def gcd(p, q):
p = 24, q = 0 if q == 0: return p
else: return gcd(q, p % q)
49
Arithmetic expression evaluation
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
operand operator
(fully parenthesized)
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
operand operator
51
fi
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the operator
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
52
Dijkstra's two-stack
algorithm
・ Value: push onto the value
stack.
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
53
Dijkstra's two-stack
algorithm
・ Value: push onto the value
stack.
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
54
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the
operator stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
55
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the
operator stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
56
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the operator
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
57
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the operator
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
58
Dijkstra's two-stack
algorithm
・ Value: push onto the value
stack.
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
59
Dijkstra's two-stack
algorithm
・ Value: push onto the value
stack.
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
60
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the
operator stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
61
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the
operator stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
62
Dijkstra's two-stack
algorithm
・ Value: push onto the value
stack.
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
63
Dijkstra's two-stack
algorithm
・ Value: push onto the value
stack.
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
64
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the operator
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
65
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the operator
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
66
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the operator
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
67
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the operator
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
68
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the
operator stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
69
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the
operator stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
70
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the operator
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
71
Dijkstra's two-stack
algorithm
・ Value: push onto the value
stack.
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
72
Dijkstra's two-stack
algorithm
・ Value: push onto the value
stack.
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
73
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the
operator stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
74
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the
operator stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
75
Dijkstra's two-stack
algorithm
・ Value: push onto the value
stack.
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
76
Dijkstra's two-stack
algorithm
・ Value: push onto the value
stack.
stack. 5
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
77
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the operator
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
78
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the operator
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
79
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the operator
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
80
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the operator
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
81
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the operator
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
82
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the operator
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
83
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the operator
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
84
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the operator
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
85
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the operator
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
86
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the operator
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
87
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the operator
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
88
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the operator
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
89
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the operator
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
90
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the operator
stack.
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
91
rithm encounters a subexpression consisting of two operands separated by an op-
erator, all surrounded by parentheses, it leaves the result of performing that opera-
tion on those operands on the operand stack. The result is the same as if that value
had appeared in the input instead of the sub-
expression, so we can think of replacing the
subexpression by the value to get an expression (1+((2+3)*(4*5)))
that would yield the same result. We can apply 1
this argument again and again until we get a +((2+3)*(4*5)))
single value. For example, the algorithm com-
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
1
putes the same value of all of these expres- + ((2+3)*(4*5)))
sions: 1 2
+3)*(4*5)))
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) ) +
operand ( 1 + ( 5 * ( operator
4 * 5 ) ) ) 1 2
( 1 + ( 5 * 20 ) ) + + 3)*(4*5)))
( 1 + 100 )
101 1 2 3
+ + )*(4*5)))
(PROGRAM 4.3.5) is an implemen-
Evaluate
Two-stack algorithm. [E. W. Dijkstra]
tation of this method. This code is a simple
1 5
*(4*5)))
+
・
+ *
Operator: push ontostringtheand operator
performs thestack.
computation to ar-
1 5 4
rive at the result. A compiler is a program that
・
+ * *5)))
Left parenthesis: ignore.
converts the string into code on a lower-level
1 5 4
・
machine that can do the job. This conversion 5)))
Right parenthesis:is pop
a moreoperator
complicatedand two values;
+ * *
process than the step-
1 5 4 5
by-step conversion used by an interpreter, but )))
push the result of itapplying that
is based on the sameoperator
underlying mechanism.
+ * *
1 5 20
Initially, Java was based on using an interpret-
to those values onto the operand stack.
er. Now, however, the Java system includes a
+ * ))
1 100
compiler that converts arithmetic expressions + )
(and, more generally, Java programs) into code
101
for the Java virtual machine, an imaginary ma-
chine that is easy to simulate on an actual com-
puter. Trace of expression evaluation (Program 4.3.5)
92
Arithmetic expression evaluation
from Stack import *
from DSA import *
ops = Stack()
vals = Stack()
while not stdIsEmpty():
s = stdReadString() % python3 Evaluate.py
if s == "(": pass ( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
elif s == "+": ops.push(s) 101.0
elif s == "*": ops.push(s)
elif s == ")":
op = ops.pop()
if op == "+": vals.push(vals.pop() + vals.pop())
elif op == "*": vals.push(vals.pop() * vals.pop())
else: vals.push(float(s))
print(vals.pop())
93
Stack-based programming languages
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
( 1 ( ( 2 3 + ) ( 4 5 * ) * ) + )
94
Stack-based programming languages
( 1 ( ( 2 3 + ) ( 4 5 * ) * ) + )
1 2 3 + 4 5 * * +
95
fi
PostScript
96
PS - Page description language
a PostScript program
%!
72 72 moveto
0 72 rlineto
72 0 rlineto
0 -72 rlineto
-72 0 rlineto
2 setlinewidth
• Explicit stack. stroke
• Graphics engine.
97
PS - Basics a PostScript program
%!
72 72 moveto
• .%!: “I am a PostScript
0 72 rlineto
72 0 rlineto
0 -72 rlineto
program.” -72 0 rlineto
2 setlinewidth
stroke
1.41421
• Braces.
72 144 moveto
72 box
288 288 moveto
144 box
• args on stack. 2 setlinewidth
stroke
100
fi
Loops, Branches
%!
\box
{
...
}
1 1 20
{ 19 mul dup 2 add moveto 72 box }
for
stroke
101
%!
72 72 translate
/kochR
{
2 copy ge { dup 0 rlineto }
{
3 div
2 copy kochR 60 rotate
2 copy kochR -120 rotate
2 copy kochR 60 rotate
2 copy kochR
} ifelse
pop pop
} def
102
R EADING L IST :
h tt p : / / a l g s 4 . c s . p r i n c e t o n . e d u
103