0% found this document useful (0 votes)
3 views

03-Stacks Queues

The document discusses data structures, specifically focusing on stacks and queues, including their definitions, operations, and implementations using arrays and linked lists. It highlights the differences between these structures, such as LIFO for stacks and FIFO for queues, and provides Python code examples for stack operations. Additionally, it addresses performance considerations, memory usage, and the trade-offs between resizing arrays and linked lists.

Uploaded by

imeddabbech
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views

03-Stacks Queues

The document discusses data structures, specifically focusing on stacks and queues, including their definitions, operations, and implementations using arrays and linked lists. It highlights the differences between these structures, such as LIFO for stacks and FIFO for queues, and provides Python code examples for stack operations. Additionally, it addresses performance considerations, memory usage, and the trade-offs between resizing arrays and linked lists.

Uploaded by

imeddabbech
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 119

Datenstrukturen

und Algorithmen

Stacks & Queues


Jens Krüger
1.3 S TACKS , AND Q UEUES

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

Fundamental data types.


• Value: collection of objects.
• Operations: insert, remove, iterate, test if empty.
• Intent is clear when we insert.

8
Which item do we remove?

• Stack. Examine the item most recently added.


stack
push
LIFO = "last in rst out"
pop

9
fi
Which item do we remove?

• Stack. Examine the item most recently added.


stack
push
LIFO = "last in rst out"
pop

• Queue. Examine the item least recently added.


queue

enqueue dequeue FIFO = " rst in rst out"

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

init() create an empty stack

push(obj : item) insert a new element onto stack


remove and return the element
obj : pop()
most recently added
bool : isEmpty() is the stack empty?

int : size() number of elements on the 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 ...

% python3 ReverseStrings.py < tinyTale.txt


... times of best the was it

13
Question: How to implement a stack
with a linked list?

A Can't be done ef ciently with a singly-linked list!

top of stack

B it was the best of None

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

of best the was it None

first

15
fi
fi
fi
Pop
save item to return

item = self.first.item

delete rst node

self.first = self.first.next
inner class

class Node: first or


f i r st or
def __init__(self, item): be be
to t o
self.item = item null
None
self.next = None
f i r st
first or
or be
be to
to null
None

return saved item

return item

16
fi
Push
save link to the list
oldfirst = self.first
oldfirst

first or
be
to
None

inner class create a new node for the beginning

class Node: self.first = self.Node(e)


def __init__(self, item):
oldfirst
self.item = item
self.next = None first not
or
be
to
None

set the connection variable in the new node

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

C times of best the was it None None None None

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

s[] it was the best of times None None None None

0 1 2 3 4 5 6 7 8 9

N capacity = 10

Defect: Stack over ows when N exceeds capacity. [stay tuned]


21
fl
Python Implementation
a cheat
class Stack: (stay tuned)
def __init__(self, capacity):
self.data = objArray(capacity)
self.N = 0
use to index into array;

then increment N def push(self, item):


self.data[self.N] = item
self.N+=1

def pop(self):
self.N-=1
return self.data[self.N]

decrement N; def isEmpty(self):


then use to index into array
return self.N == 0

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

Problem: Requiring client to provide capacity does not


implement API!
Q: How to grow and shrink array?

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!

• Need to copy all items to a new array, for each operation.


• Array accesses to insert rst N items
infeasible for large N

= N + (2 + 4 + … + 2(N – 1)) ~ N 2

1 array access 2(k–1) array accesses to expand to size k


per push (ignoring cost to create new array)

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 __resize(self, capacity):


copy = objArray(capacity)
for i in range(self.N):
copy[i] = self.data[i]
self.data = copy

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 __resize(self, capacity):


copy = objArray(capacity)
for i in range(self.N):
copy[i] = self.data[i]
self.data = copy

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

Array accesses to insert rst N = 2i items. N + (2 + 4 + 8 + … + N) ~ 3N.

1 array access k array accesses to double to size k


per push (ignoring cost to create new array)

29
fi
Array accesses to insert rst N = 2i items. N + (2 + 4 + 8 + … + N) ~ 3N.

1 array access k array accesses to double to size k


per push (ignoring cost to create new array)

128

cost (array accesses)


one gray dot 128
for each operation

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?

• push(): double size of array s[] when array is full.


• pop(): halve size of array s[] when array is one-half full.

31
Too expensive!

• Consider push-pop-push-pop-… sequence when array is full.


• Each operation takes time proportional to N.

N=5 to be or not to None None None

N=4 to be or not

N=5 to be or not to None None None

N=4 to be or not

32
Ef cient solution?

33
fi
Ef cient solution?

• push(): double size of array s[] when array is full.


• pop(): halve size of array s[] when array is one-quarter full.

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

order of growth of running time


for resizing stack with N items

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

order of growth of running time


for resizing stack with N items

34
Memory Usage
• Proposition. Uses between ~ ⋅N and ~ 4 ⋅N bytes
overhead to represent a stack with N items.
• ~ ⋅N when full.

• ~ 4 ⋅N when one-quarter full.


class Stack:
def __init__(self):
self.data = objArray(2) object-size " " ⋅ array size
self.N = 0

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

init() create an empty queue

enqueue(obj : item) insert a new element onto queue

remove and return the element


obj : dequeue()
least recently added

bool : isEmpty() is the queue empty?

int : size() number of elements on the queue

dequeue

38
Question: How to implement a
Queue with a linked list?

A Can't be done ef ciently with a singly-linked list!

back of queue front of queue

B
times of best the was it None

front of queue back of queue

C
it was the best of times None

39
fi
”C“
• Maintain one pointer rst to rst node in a singly-linked list.

• Maintain another pointer last to last node.

• Dequeue from rst.

• Enqueue after last.

front of queue back of queue

it was the best of times None

rst last

40
fi
fi
fi
fi
Dequeue
save item to return

item = self.first.item

delete rst node

self.first = self.first.next
inner class

class Node: first or


f i r st or
def __init__(self, item): be be
to t o
self.item = item null
None
self.next = None
f i r st
first or
or be
be to
to null
None

return saved item

return item

Remark. Identical code to linked-list stack pop().


41
fi
Enqueue
save a link to the last node
oldlast = self.last

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

link the new node to the end of the list

oldlast.next = self.last
oldlast
last
first to
be
or
not
None

Inserting a new node at the end of a linked list


class ListQueue:
class Node:
def __init__(self, item):
self.item = item
self.next = 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?

A Can't be done ef ciently with an array!

front of queue back of queue

B
it was the best of times None None None None

0 1 2 3 4 5 6 7 8 9

back of queue front of queue

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].

• dequeue(): remove item from data[head].

• Update head and tail modulo the capacity.

• Add resizing array. How?


front of queue back of queue

q[] None None the best of times None None None None

0 1 2 3 4 5 6 7 8 9

head tail capacity = 10

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

How a compiler implements a function.


• Function call: push local environment and return address.
• Return: pop return address and local environment.

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)

gcd (192, 24)


def gcd(p, q):
p = 192, q = 24 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 (192, 24)


def gcd(p, q):
p = 192, q = 24 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

Two-stack algorithm. [E. W. Dijkstra]


・Value: push onto the value stack.
・Operator: push onto the operator stack.
・Left parenthesis: ignore.
・Right parenthesis: pop operator and two values;
push the result of applying that operator
to those values onto the operand stack.
50
Dijkstra's two-stack
algorithm

in x expression value stack operator stack

(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.

・ Left parenthesis: ignore.


・ Right parenthesis: pop operator
and two values; push the result of

applying that operator to those

values onto the operand stack.


value stack operator stack

( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )

52
Dijkstra's two-stack
algorithm
・ Value: push onto the value

stack.

・ Operator: push onto the operator

stack.

・ Left parenthesis: ignore.


・ Right parenthesis: pop operator
and two values; push the result of

applying that operator to those

values onto the operand stack. value stack operator stack

( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )

53
Dijkstra's two-stack
algorithm
・ Value: push onto the value

stack.

・ Operator: push onto the operator

stack.

・ Left parenthesis: ignore.


・ Right parenthesis: pop operator
and two values; push the result of
1
applying that operator to those

values onto the operand stack. value stack operator stack

( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )

54
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the
operator stack.

・ Left parenthesis: ignore.


・ Right parenthesis: pop operator
and two values; push the result of

applying that operator to those 1


values onto the operand stack.
value stack 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.

・ Left parenthesis: ignore.


・ Right parenthesis: pop operator
and two values; push the result of

applying that operator to those 1 +


values onto the operand stack.
value stack 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.

・ Left parenthesis: ignore.


・ Right parenthesis: pop operator
and two values; push the result of

applying that operator to those 1 +


values onto the operand stack.
value stack 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.

・ Left parenthesis: ignore.


・ Right parenthesis: pop operator
and two values; push the result of

applying that operator to those 1 +


values onto the operand stack.
value stack operator stack

( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )

58
Dijkstra's two-stack
algorithm
・ Value: push onto the value

stack.

・ Operator: push onto the operator

stack.

・ Left parenthesis: ignore.


・ Right parenthesis: pop operator
and two values; push the result of
1 +
applying that operator to those

values onto the operand stack. value stack operator stack

( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )

59
Dijkstra's two-stack
algorithm
・ Value: push onto the value

stack.

・ Operator: push onto the operator

stack.

・ Left parenthesis: ignore.


・ Right parenthesis: pop operator 2
and two values; push the result of
1 +
applying that operator to those

values onto the operand stack. value stack operator stack

( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )

60
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the
operator stack.

・ Left parenthesis: ignore.


・ Right parenthesis: pop operator
and two values; push the result of 2

applying that operator to those 1 +


values onto the operand stack.
value stack 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.

・ Left parenthesis: ignore.


・ Right parenthesis: pop operator
and two values; push the result of 2 +

applying that operator to those 1 +


values onto the operand stack.
value stack operator stack

( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )

62
Dijkstra's two-stack
algorithm
・ Value: push onto the value

stack.

・ Operator: push onto the operator

stack.

・ Left parenthesis: ignore.


・ Right parenthesis: pop operator 2 +
and two values; push the result of
1 +
applying that operator to those

values onto the operand stack. value stack operator stack

( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )

63
Dijkstra's two-stack
algorithm
・ Value: push onto the value

stack.

・ Operator: push onto the operator

stack.

・ Left parenthesis: ignore. 3

・ Right parenthesis: pop operator 2 +


and two values; push the result of
1 +
applying that operator to those

values onto the operand stack. value stack operator stack

( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )

64
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the operator
stack.

・ Left parenthesis: ignore.


・ Right parenthesis: pop 3
operator and two values; push
2 +
the result of applying that
1 +
operator to those values onto

the operand stack. value stack 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.

・ Left parenthesis: ignore.


・ Right parenthesis: pop 3 + 2
operator and two values; push

the result of applying that


1 +
operator to those values onto

the operand stack. value stack 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.

・ Left parenthesis: ignore.


・ Right parenthesis: pop 3 + 2 = 5
operator and two values; push

the result of applying that


1 +
operator to those values onto

the operand stack. value stack 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.

・ Left parenthesis: ignore.


・ Right parenthesis: pop
operator and two values; push
5
the result of applying that
1 +
operator to those values onto

the operand stack. value stack 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.

・ Left parenthesis: ignore.


・ Right parenthesis: pop operator
and two values; push the result of 5

applying that operator to those 1 +


values onto the operand stack.
value stack 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.

・ Left parenthesis: ignore.


・ Right parenthesis: pop operator
and two values; push the result of 5 *

applying that operator to those 1 +


values onto the operand stack.
value stack 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.

・ Left parenthesis: ignore.


・ Right parenthesis: pop operator
and two values; push the result of 5 *

applying that operator to those 1 +


values onto the operand stack.
value stack operator stack

( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )

71
Dijkstra's two-stack
algorithm
・ Value: push onto the value

stack.

・ Operator: push onto the operator

stack.

・ Left parenthesis: ignore.


・ Right parenthesis: pop operator 5 *
and two values; push the result of
1 +
applying that operator to those

values onto the operand stack. value stack operator stack

( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )

72
Dijkstra's two-stack
algorithm
・ Value: push onto the value

stack.

・ Operator: push onto the operator

stack.

・ Left parenthesis: ignore. 4

・ Right parenthesis: pop operator 5 *


and two values; push the result of
1 +
applying that operator to those

values onto the operand stack. value stack operator stack

( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )

73
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the
operator stack.

・ Left parenthesis: ignore.


4
・ Right parenthesis: pop operator
and two values; push the result of 5 *

applying that operator to those 1 +


values onto the operand stack.
value stack 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.

・ Left parenthesis: ignore.


4 *
・ Right parenthesis: pop operator
and two values; push the result of 5 *

applying that operator to those 1 +


values onto the operand stack.
value stack operator stack

( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )

75
Dijkstra's two-stack
algorithm
・ Value: push onto the value

stack.

・ Operator: push onto the operator

stack.

・ Left parenthesis: ignore. 4 *

・ Right parenthesis: pop operator 5 *


and two values; push the result of
1 +
applying that operator to those

values onto the operand stack. value stack operator stack

( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )

76
Dijkstra's two-stack
algorithm
・ Value: push onto the value

stack.

・ Operator: push onto the operator

stack. 5

・ Left parenthesis: ignore. 4 *

・ Right parenthesis: pop operator 5 *


and two values; push the result of
1 +
applying that operator to those

values onto the operand stack. value stack operator stack

( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )

77
Dijkstra's two-stack
algorithm
・ Value: push onto the value stack.
・ Operator: push onto the operator
stack.

・ Left parenthesis: ignore. 5

・ Right parenthesis: pop 4 *


operator and two values; push
5 *
the result of applying that
1 +
operator to those values onto

the operand stack. value stack 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.

・ Left parenthesis: ignore.


・ Right parenthesis: pop 5 * 4
operator and two values; push
5 *
the result of applying that
1 +
operator to those values onto

the operand stack. value stack 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.

・ Left parenthesis: ignore.


・ Right parenthesis: pop 5 * 4 = 20
operator and two values; push
5 *
the result of applying that
1 +
operator to those values onto

the operand stack. value stack 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.

・ Left parenthesis: ignore.


・ Right parenthesis: pop 20
operator and two values; push
5 *
the result of applying that
1 +
operator to those values onto

the operand stack. value stack 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.

・ Left parenthesis: ignore.


・ Right parenthesis: pop 20
operator and two values; push
5 *
the result of applying that
1 +
operator to those values onto

the operand stack. value stack 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.

・ Left parenthesis: ignore.


・ Right parenthesis: pop 20 * 5
operator and two values; push

the result of applying that


1 +
operator to those values onto

the operand stack. value stack 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.

・ Left parenthesis: ignore.


・ Right parenthesis: pop 20 * 5 = 100
operator and two values; push

the result of applying that


1 +
operator to those values onto

the operand stack. value stack 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.

・ Left parenthesis: ignore.


・ Right parenthesis: pop
operator and two values; push
100
the result of applying that
1 +
operator to those values onto

the operand stack. value stack 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.

・ Left parenthesis: ignore.


・ Right parenthesis: pop
operator and two values; push
100
the result of applying that
1 +
operator to those values onto

the operand stack. value stack 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.

・ Left parenthesis: ignore.


・ Right parenthesis: pop 100 + 1
operator and two values; push

the result of applying that

operator to those values onto

the operand stack. value stack 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.

・ Left parenthesis: ignore.


・ Right parenthesis: pop 100 + 1 = 101
operator and two values; push

the result of applying that

operator to those values onto

the operand stack. value stack 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.

・ Left parenthesis: ignore.


・ Right parenthesis: pop
operator and two values; push

the result of applying that


101
operator to those values onto

the operand stack. value stack 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.

・ Left parenthesis: ignore.


・ Right parenthesis: pop operator
and two values; push the result of

applying that operator to those 101


values onto the operand stack.
value stack 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.

・ Left parenthesis: ignore.


・ Right parenthesis: pop operator
and two values; push the result of

applying that operator to those 101


values onto the operand stack.
result

( 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)))
+

・ Value: push onto example


the value of anstack.
interpreter : a program that in-
terprets the computation specified by a given
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

Observation 1. Dijkstra's two-stack


algorithm computes the same value if the
operator occurs after the two values.

( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )

( 1 ( ( 2 3 + ) ( 4 5 * ) * ) + )
94
Stack-based programming languages

Observation 2. All of the parentheses are


redundant (with slight code modi cation)!

( 1 ( ( 2 3 + ) ( 4 5 * ) * ) + )

1 2 3 + 4 5 * * +
95
fi
PostScript

Simple virtual machine, but not a toy.


• Easy to specify published page.
• Easy to implement in printers.
• Revolutionized the publishing world.

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

• Full computational model its output

• 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

• Literal: “push me on the


stack.” its output

• Function calls take arguments


from stack.
• Turtle graphics built in.
98
PS - Advanced
%!

• Data types. /Helvetica-Bold findfont 16 scalefont setfont


72 168 moveto

• Basic: integer, oating point, boolean, ...


(Square root of 2:) show
72 144 moveto
2 sqrt 10 string cvs show

• Graphics: font, path, curve, ....


• Full set of built-in operators.
• Text and strings.
• Full font support.
• show (display a string, using current font). Square root of 2:

1.41421

• cvs (convert anything to a string).


99
fl
Variables & Functions
%!
/box
• Identi ers start with / {
/sz exch def
0 sz rlineto

• def operator associates id sz 0 rlineto


0 sz neg rlineto

with value. sz neg 0 rlineto


} def

• 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

0 0 moveto 81 243 kochR


0 81 moveto 27 243 kochR
0 162 moveto 9 243 kochR
0 243 moveto 1 243 kochR
stroke

102
R EADING L IST :

1.4 A NALYSIS OF A LGORITHMS

h tt p : / / a l g s 4 . c s . p r i n c e t o n . e d u

103

You might also like