490 likes | 506 Views
Revisit the concept of queues represented by a triple to achieve a purely functional solution with constant time complexities. Analyze and generalize for fully persistent deques.
E N D
Lets revisit queues first. Only pop & inject It will be possible to generalize our solution in various ways so we can get a purely functional solution with O(1) time per push, pop, inject, eject, and catenate !
suffix prefix Queues revisited Represent a queue over A by a triple (prefix, queue over AA, suffix) Prefix and suffix contain at most 2 elements of the queue. suffix prefix
Inject suffix prefix suffix prefix
Inject suffix prefix suffix suffix prefix
suffix suffix Inject suffix prefix suffix suffix prefix
suffix suffix suffix Inject suffix prefix suffix suffix prefix
Inject suffix prefix suffix suffix suffix suffix prefix suffix
Inject suffix prefix suffix suffix suffix suffix suffix prefix suffix
suffix suffix suffix suffix Inject suffix prefix suffix suffix suffix suffix suffix prefix suffix
Inject suffix prefix suffix suffix suffix suffix suffix suffix prefix suffix suffix suffix suffix
Inject suffix prefix suffix suffix suffix suffix suffix suffix suffix prefix suffix suffix suffix suffix
result = inject(x,d) If (suffix(d) = (a,b) ) then d’ := inject(child(d),(a,b)) child(d) := d’ suffix(d) := empty endif allocate(result) prefix(result) := prefix(d) child(result) := child(d) suffix(result) := allocate(suffix(d) + x)
(x,r)= pop(Q) suffix prefix suffix prefix
(x,r)= pop(Q) suffix prefix prefix suffix prefix
(x,r)= pop(Q) suffix prefix prefix suffix prefix
prefix prefix (x,r)= pop(Q) suffix prefix prefix suffix prefix Formal definition of the algorithm is similar to inject, do by yourself.
How do we analyze this ? We want each call to inject (pop) that triggered another call to inject (pop) to release one unit of potential. ==> suffix suffix suffix ==> prefix prefix prefix
Fully persistent queues -- analysis Define a suffix with 2 elements as red. Define a prefix with 0 elements as red. Otherwise the buffer is green. ==> suffix suffix suffix ==> prefix prefix prefix
Fully persistent queues -- analysis Maybe = # (red buffers) ? Red buffers still exists for other queues after the change. ==> suffix suffix suffix ==> prefix prefix prefix
Fully persistent queues -- analysis Define a queue/subqueue as either rr, rg, gg Either an rr queue dies and we get two rg queues or an rg queue dies and we get two gg queues ==> suffix suffix suffix ==> prefix prefix prefix
Fully persistent queues -- analysis = 3# (rr) + #(rg) ==> suffix suffix suffix ==> prefix prefix prefix
Fully persistent deques How do we generalize this to allow insertions/deletions from both sides ? Increase the size of the buffers to 0-3. Everything else is the same, when a buffer is full we may have to do a recursive push/inject, when a buffer is empty we may have to do a recursive eject/pop. Analysis: Define prefix/suffix to be red if it contains either 0 or 3 elements.
Worst-case bounds/purely functional implementation Increase the size of the buffers to 0-5. Define a buffer as red if it contains 0 or 5 elements yellow if it contains 1 or 4 elements green if it contains 2 or 3 elements Let green < yellow < red and define the color of a deque as the maximum of the colors of its buffers.
1 0 2 Purely functional deques Let green = 0 red = 2 yellow = 1 Think of the stack of queues as a redundant binary counter. prefix suffix suffix prefix suffix prefix
1 0 2 Purely functional deques Push/pop/inject/eject may increase the least significant digit. Then you need to do a fix. Fix: fill an empty buffer by popping from the corresponding buffer of the subqueue. Similarly empty a full buffer. prefix suffix suffix prefix suffix prefix
Representation 2 1 1 0 1 2 1 1 We need a purely functional representation of these counters.
2 0 0 1 1 Stack of stacks representation 2 0 0 1 1 1 1 1 Only a constant number of new nodes per increment.
Purely functional deques 2 0 1 1
Adding catenation We will do only steques (no eject) prefix, steque of pairs, suffix pair = (prefix, steque of pairs) prefix contains 2-6 elements suffix contains 1-3 elements
Push Inject is similar.
Pop catenate There are two more cases. The analysis is similar to what we have seen before
Summary In the paper: The full set of operations. Together with some generalization of the counters you can get a purely functional implementation with worst case performance.
Alternative approaches Put the items at the leaves of a tree which is at least binary. Catenation is done via linking: left linking
Alternative approaches (cont.) Alternative forms of linking right linking symmetric linking
Alternative approaches (cont.) How to pop ? v flip v <==> u w Left path reversal: Do right flips bottom-up on the parent of the leftmost leaf.
Alternative approaches (cont.) After the left path reversal: r` r Create a new root for the queue after the pop. Represent children lists as deques (or steques) so each flip takes constant time, and duplicating the root takes constant time
Alternative approaches (cont.) Can do eject symmetrically, do push and inject via catenation Can analyze only for steques (when eject is not allowed). (Okasaki FOCS’96) The performance in general is not known ??
Alternative approaches (cont.) To avoid pushing down stuff on the right side of the tree when doing a left path reversal replace the flips with the following v v <==> u w z w Performance of this is not known ??
Alternative approaches (cont.) Can we avoid the extra persistent machanism to represent children lists ? w w <==> v z y u Here stacks suffice if there is no eject.
z y D C x A B Alternative approaches (cont.) Use binary trees. Link by symmetric linking. To pop do a splay on the parent of the leftmost leaf and then delete it and its parent. x ==> A y B z C D