1 Abstract Data Types Queue + Dequeue Amortized analysis
2 Queue Inject(x,Q) : Insert last element x into Q Pop(Q) : Delete the first element in Q Empty?(Q): Return yes if Q is empty Front(Q): Return the first element in Q Size(Q) Make-queue()
3 The Queue Data Abstraction inject
4 The Queue Data Abstraction inject pop First in, First out (FIFO).
5 Using an array A 5 A[0] A[1] t pop(Q) A[2] A[N-1]
6 Using an array 1425 A A[0] A[1] t pop(Q) A[2] A[N-1]
7 Using an array 1425 A A[0] A[1] t A[2] A[N-1] This would be inefficient if we insist that elements span a prefix of the array
8 Using an array A 5 A[0] A[1] r A[2] A[N-1] f A A[0] A[1] r A[2] f Empty queue f=r
9 Using an array A 5 A[0] A[1] r pop(Q) A[2] A[N-1] f
10 Using an array 142 A 5 A[0] A[1] A[2] A[N-1] f r pop(Q) inject(5,Q)
11 Using an array 142 A 55 A[0] A[1] A[2] A[N-1] f r pop(Q) inject(5,Q)
12 Using an array 142 A 555 A[0] A[1] A[2] A[N-1] f r pop(Q) inject(5,Q) pop(Q)
13 Using an array 2 A 555 A[0] A[1] A[2] A[N-1] f r pop(Q) inject(5,Q) pop(Q) pop(Q), inject(5,Q), pop(Q), inject(5,Q),……….
14 Using an array A 5555 A[0] A[1] r A[2] A[N-1] f pop(Q) inject(5,Q) pop(Q) pop(Q), inject(5,Q), pop(Q), inject(5,Q),……….
15 Make the array “circular” 5 5 A 55 A[0] A[1] r A[2] A[N-1] f Pop(Q), inject(5,Q), pop(Q), inject(5,Q),……….
16 Operations empty?(Q): return (f = r) top(Q): if empty?(Q) then error else return A[f] 142 A 5 A[0] A[1] A[2] A[N-1] f r
17 Operations size(Q): if (r >= f) then return (r-f) else return N-(f-r) 142 A 5 A[0] A[1] A[2] A[N-1] f r
18 Operations size(Q): if (r >= f) then return (r-f) else return N-(f-r) 5 5 A 55 A[0] A[1] r A[2] A[N-1] f
19 Pop pop(Q) 142 A 5 A[0] A[1] A[2] f r pop(Q): if empty?(Q) then error else e ← A[f] f ← (f + 1) mod N return (e)
20 Pop pop(Q): if empty?(Q) then error else e ← A[f] f ← (f + 1) mod N return (e) pop(Q) 142 A 5 A[0] A[1] A[2] f r
21 Push inject(x,Q): if size(Q) = N-1 then error else A[r] ← x r ← (r+1) mod N inject(5,Q) 42 A 5 A[0] A[1] A[2] f r
22 Push inject(x,Q): if size(Q) = N-1 then error else A[r] ← x r ← (r+1) mod N inject(5,Q) 42 A 55 A[0] A[1] A[2] f r
23 Implementation with lists head size=3 tail inject(4,Q)
24 Implementation with lists head size=3 tail inject(4,Q) 4
25 Implementation with lists head size=3 tail inject(4,Q) 4 Complete the details by yourself
26 Can one Implement A Queue with stacks? S2S2 You are given the STACK ABSTRACT data structure (1, 2.. as many as you want) Can you use it to implement a queue S1S Q
27 Implementation of Queue with stacks size=5 13 S1S1 S2S2 inject(x,Q): push(x,S 2 ); size ← size inject(2,Q)
28 Implementation with stacks size=5 13 S1S1 S2S2 inject(x,Q): push(x,S 2 ); size ← size inject(2,Q)
29 Implementation of a Queue with stacks size=6 13 S1S1 S2S inject(2,Q) inject(x,Q): push(x,S 2 ); size ← size + 1
30 Pop size=6 S1S1 S2S2 pop(Q): if empty?(Q) error if empty?(S 1 ) then move(S 2, S 1 ) pop( S 1 ); size ← size pop(Q) 13
31 Pop size=6 S1S1 S2S2 pop(Q): if empty?(Q) error if empty?(S 1 ) then move(S 2, S 1 ) pop( S 1 ); size ← size pop(Q)
32 Pop size=5 S1S1 S2S2 pop(Q): if empty?(Q) error if empty?(S 1 ) then move(S 2, S 1 ) pop( S 1 ); size ← size pop(Q) pop(Q)
33 Pop size=5 S1S1 S2S2 pop(Q): if empty?(Q) error if empty?(S 1 ) then move(S 2, S 1 ) pop( S 1 ); size ← size pop(Q) pop(Q) 2
34 Pop size=5 S1S1 S2S2 pop(Q): if empty?(Q) error if empty?(S 1 ) then move(S 2, S 1 ) pop( S 1 ); size ← size pop(Q) pop(Q) 2 21
35 Pop size=5 S1S1 S2S2 pop(Q): if empty?(Q) error if empty?(S 1 ) then move(S 2, S 1 ) pop( S 1 ); size ← size pop(Q) pop(Q)
36 Pop size=5 S1S1 S2S2 pop(Q): if empty?(Q) error if empty?(S 1 ) then move(S 2, S 1 ) pop( S 1 ); size ← size -1 5 pop(Q) pop(Q)
37 Pop size=5 S1S1 S2S2 pop(Q): if empty?(Q) error if empty?(S 1 ) then move(S 2, S 1 ) pop( S 1 ); size ← size -1 pop(Q) pop(Q)
38 Pop size=4 S1S1 S2S2 pop(Q): if empty?(Q) error if empty?(S 1 ) then move(S 2, S 1 ) pop( S 1 ); size ← size -1 pop(Q) pop(Q)
39 move(S 2, S 1 ) while not empty?(S 2 ) do x ← pop(S 2 ) push(x,S 1 )
40 Analysis O(n) worst case time per operation
41 Amortized Analysis How long it takes to perform m operations on the worst case ? O(nm) Is this tight ?
42 Key Observation An expensive operation cannot occur too often !
43 Amortized complexity THM: If we start with an empty queue and perform m operations then it takes O(m) time Proof: No element moves from S2 to S1 Entrance at S1, exit at S2. Every element: 1.Enters S1 exactly once 2.Moves from S1 to S2 at most once 3.Exits S2 at most once #ops per element ≤ 3 m operations #elements ≤ m work ≤ 3 m S2S2 S1S
44 The potential formalism The potential is in fact the bank
45 Amortized(op) = actual(op) + Define Define: a potential function
46 Amortized(op 1 ) = actual(op 1 ) + 1 - 0 Amortized(op 2 ) = actual(op 2 ) + 2 - 1 Amortized(op n ) = actual(op n ) + n - (n-1) ………… + i Amortized(op i ) = i actual(op i ) + n - 0 i Amortized(op i ) i actual(op i ) if n - 0 0
47 Potential based Proof (on your own) Consider Recall that: Amortized(op) = actual(op) + ΔΦ This is O(1) if a move does not occur Say we move S 2 : Then the actual time is |S 2 | + O(1) ΔΦ = -|S 2 | So the amortized time is O(1) Think of Φ as accumulation of easy operations covering for future potential “damage”
48 Conclusion THM: If we start with an empty queue and perform m operations then it takes O(m) time
49 Double ended queue (deque) Push(x,D) : Insert x as the first in D Pop(D) : Delete the first element of D Inject(x,D): Insert x as the last in D Eject(D): Delete the last element of D Size(D) Empty?(D) Make-deque()
50 Implementation with doubly linked lists 5 head size=2 tail 13 x.next x.element x.prev x
51 Empty list head size=0 tail We use two sentinels here to make the code simpler
52 Push push(x,D): n = new node n.element ← x n.next ← head.next (head.next).prev ← n head.next ← n n.prev ← head size ← size head size=1 tail
53 push(x,D): n = new node n.element ← x n.next ← head.next (head.next).prev ← n head.next ← n n.prev ← head size ← size head size=1 tail push(4,D) 4
54 push(x,D): n = new node n.element ← x n.next ← head.next (head.next).prev ← n head.next ← n n.prev ← head size ← size head size=1 tail push(4,D) 4
55 push(x,D): n = new node n.element ← x n.next ← head.next (head.next).prev ← n head.next ← n n.prev ← head size ← size head size=1 tail push(4,D) 4
56 push(x,D): n = new node n.element ← x n.next ← head.next (head.next).prev ← n head.next ← n n.prev ← head size ← size head size=2 tail push(4,D) 4
57 Implementations of the other operations are similar Try by yourself
58 Back to deques Alternative implementation using stacks
59 Implementation with stacks size=5 13 S1S1 S2S2 push(x,D): push(x,S 1 ) push(2,D)
60 Implementation with stacks size= S1S1 S2S2 push(x,D): push(x,S 1 ) push(2,D)
61 Pop size=6 S1S1 S2S2 pop(D): if empty?(D) error if empty?(S 1 ) then split(S 2, S 1 ) pop( S 1 ) pop(D)
62 Pop size=5 S1S1 S2S2 pop(D): if empty?(D) error if empty?(S 1 ) then split(S 2, S 1 ) pop( S 1 ) pop(D) pop(D)
63 Pop size=4 S1S1 S2S2 pop(D): if empty?(D) error if empty?(S 1 ) then split(S 2, S 1 ) pop( S 1 ) pop(D) pop(D)
64 Pop size=4 S1S1 S2S2 pop(D): if empty?(D) error if empty?(S 1 ) then split(S 2, S 1 ) pop( S 1 ) pop(D)
65 Pop size=4 S1S1 S2S2 pop(D): if empty?(D) error if empty?(S 1 ) then split(S 2, S 1 ) pop( S 1 ) pop(D)
66 Pop size=4 S1S1 S2S2 pop(D): if empty?(D) error if empty?(S 1 ) then split(S 2, S 1 ) pop( S 1 ) pop(D) 5 4
67 Pop size=4 S1S1 S2S2 pop(D): if empty?(D) error if empty?(S 1 ) then split(S 2, S 1 ) pop( S 1 ) pop(D) 4
68 Pop size=3 S1S1 S2S2 pop(D): if empty?(D) error if empty?(S 1 ) then split(S 2, S 1 ) pop( S 1 ) pop(D) 4
69 Split S1S1 S2S2 S3S3
70 Split S1S1 S2S2 S3S3 21
71 Split 5 4 S1S1 S2S2 S3S
72 Split 5 S1S1 S2S2 S3S
73 Split S1S1 S2S2 S3S
74 Split S1S1 S2S2 S3S
75 Split S1S1 S2S2 S3S
76 Split (same thing in reverse) 5 4S1S1 S2S2 S3S3
77 split(S 2, S 1 ) S 3 ← make-stack() d ← size(S 2 ) while (i ≤ ⌊ d/2 ⌋ ) do x ← pop(S 2 ) push(x,S 3 ) i ← i+1 while (i ≤ ⌈ d/2 ⌉ ) do x ← pop(S 2 ) push(x,S 1 ) i ← i+1 while (i ≤ ⌊ d/2 ⌋ ) do x ← pop(S 3 ) push(x,S 2 ) i ← i+1
78 Analysis O(n) worst case time per operation
79 Thm: If we start with an empty deque and perform m operations then it takes O(m) time
80 Amortized Analysis How long it takes to perform m operations on the worst case ? O(nm) Really ?
81 Key Observation An expensive operation cannot occur too often !
82 A better bound Consider This is O(1) if no splitting occurs Say we split S 1 : Then the actual time is |S 1 | + O(1) ΔΦ = -|S 1 | (S 2 empty) So the amortized time is O(1) Recall that: Amortized(op) = actual(op) + ΔΦ Think of Φ as accumulation of easy operations covering for future potential “damage”