Download A Fully Retroactive Priority Queues

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts

Lattice model (finance) wikipedia , lookup

Quadtree wikipedia , lookup

B-tree wikipedia , lookup

Red–black tree wikipedia , lookup

Binary tree wikipedia , lookup

Interval tree wikipedia , lookup

Binary search tree wikipedia , lookup

Transcript
A
Fully Retroactive Priority Queues
Demaine et. al. [Demaine et al. 2007] introduced fully retroactive data structures as structures that allow queries and updates
√
to be performed not only in the present but also in the past. They provided an implementation of a O( m log m)-time
fully retroactive priority queue and posed as an open question whether a polylog-time implementation can be constructed
[Demaine et al. 2007]. In this paper, we answer this open question by constructing a new polylog-implementation of a fully
retroactive priority queue: a data structure for which it is possible to add elements, delete the minimum element, or list all
elements in the data structure, at any point in time. The data structure will automatically keep all queries to the data structure
consistent with past updates, even when the past changes. All updates and queries to the data structure are polylogarithmic,
with a query time of O(log2 m log log m) amortized, update time of O(log2 m) amortized, and O(m log m) space usage. We
also discuss a novel general technique for converting partially retroactive data structures to fully retroactive data structures
for which our implementation of the fully retroactive priority queue is an instance.
Additional Key Words and Phrases: retroactivity, data structures, priority queues
1. INTRODUCTION
Demaine et. al. [Demaine et al. 2007] introduced the concept of retroactive data structures to
represent structures that allow going back in time to perform updates and queries. They presented
several examples of retroactive structures
√ with optimal runtimes but the fully retroactive priority
queue as stated in their paper has a O( m log m) runtime, certainly not the logarithmic runtime we
look for in such structures.
This paper discusses a novel formulation of the fully retroactive priority queue that achieves a
much lower, polylogarithmic time bound and suggests a method for converting partially retroactive
data structures to fully retroactive structures that could result in a lower overhead than the method
suggested in [Demaine et al. 2007] for some structures.
We define a priority queue to be a data structure that supports the following operations:
• INSERT(x): insert an element with key x into the priority queue.
• DELETE(x): delete an element with key x from the priority queue.
• DELETE - MIN(): delete and return the element in the priority queue with the smallest key.
We define a partially retroactive priority queue to be a data structure that supports the following
operations:
• I NSERT-O P(o,t): insert a priority queue operation o into the retroactive priority queue’s timeline
at time t.
• D ELETE -O P(o,t): delete a priority queue operation o from the retroactive priority queue’s timeline at time t.
• F IND -M IN(): return the element in the priority queue with the smallest key at the end of the
queue’s timeline.
The I NSERT-O P and D ELETE -O P operations for a partially retroactive priority queue are used to
insert or remove operations in the priority queue’s timeline. One example of a possible call to the
retroactive priority queue might be I NSERT-O P(DELETE - MIN, t) which adds a DELETE - MIN call to
the timeline at time 5. While the data structure can be modified at any time, it can only be queried at
the latest time. In general, we will use the term partially retroactive to refer to data structures which
can be modified at any time, but only queried at the end of their timelines. In addition, as a matter of
convention, we capitalize the names of functions that are time-dependent (can be called at any time
along the timeline), but we will leave the names of functions that are not time-dependent entirely in
lowercase.
The above description of partial retroactivity is a weaker requirement than full retroactivity,
which allows both modification of the data structure and querying of the data structure at any point
in time. A fully retroactive priority queue is a data structure that could have the following calls
made to it:
A:2
Fig. 1: (a)
Fig. 2: (b)
Fig. 3: (c)
Fig. 4: Arrow diagrams for Q. Green horizontal lines represent elements existing in the priority
queue across time. Red vertical lines that come from negative infinity represent DELETE - MIN calls.
A green and a red line both end when they collide. When a new DELETE - MIN call is inserted at
time step 5, elements of the priority queue must be updated. In the bottom diagram, a DELETE - MIN
call inserted at time 5 causes a cascade of changes at times 6, 7, and 8. The DELETE - MIN at time 6,
which had previously deleted key 10, now deletes key 20. The DELETE - MIN at time 7, which had
previously deleted key 20, now deletes key 30.
• I NSERT-O P(o,t): inserts a priority queue call o into the retroactive priority queue’s timeline at
time t.
• D ELETE -O P(o,t): deletes a priority queue operation o from the retroactive priority queue’s timeline at time t.
• F IND -M IN(t): returns the element in the priority queue with the smallest key at time t.
This paper is concerned with the description of a fully retroactive priority queue. The architecture
of the fully retroactive priority queue described is heavily reliant on a partially retroactive priority
queue, however, so this paper will include a brief description of the workings of partially retroactive
priority queues, as well.
2. PARTIALLY RETROACTIVE PRIORITY QUEUES
The problem of maintaining a priority queue across time is challenging. Even maintaining a partially
retroactive priority queue is non-trivial and a fundamental contribution.
Consider a priority queue Q containing four elements, with keys 10, 20, 30, and 40 inserted at
times 1, 2, 3, and 4, respectively. At times 6, 7, and 8, a DELETE - MIN call was made to the priority
queue, resulting in the deletion of keys 10, 20, and 30 in the order that they were added. A timeline
of the priority queue is shown below as an“arrow diagram”–a diagram intended to provide a visual
representation of the contents of the priority queue after different update calls are made.
A:3
Now consider what happens if another DELETE - MIN call is inserted at time 5. This causes a
“cascade” of changes, which implies that we may need to do a linear amount of work to maintain
the data structure if we do it naively. A more clever method is required.
Naturally, we cannot hope to directly update this picture each time our priority queue changes
and hope to retain efficient performance. A long cascade of this sort will incur a linear-cost update:
much too expensive. Fortunately, in the partially retroactive case, we only need to be concerned
with having an accurate picture of the data structure in the present, after all the updates have already
been processed. The “net effect” of I NSERT-O P(DELETE - MIN, 5) is that at time 8, key 40 was
deleted from the priority queue; it is this change that we must try to efficiently compute.
To do this, Demaine et. al. [Demaine et al. 2007] took advantage of the fact that the minimum
element was never queried at a particular time t. Thus, there is no need to maintain an updated
priority queue at any other time step besides the present. This lets them delete and add elements to
the list of elements that exist in the priority queue at present, Qnow , without needing to update any
previous versions.
Specifically, they proved that given an operation I NSERT-O P(INSERT(k), t), the element to be
inserted in Qnow is either k or the maximum element out of all the elements that were deleted after
time t, depending on which is larger. Similarly, for the operation D ELETE -O P(DELETE - MIN, t), the
maximum element from among the elements that were deleted after time t is inserted into Qnow .
For I NSERT-O P(DELETE - MIN, t), they considered all sets Qt 0 where t 0 > t and Qt 0 ⊆ Qnow and
removed the minimum element. Finally, for D ELETE -O P(INSERT(k), t), they remove k if it is in
Qnow , otherwise they performed I NSERT-O P(DELETE - MIN, t) [Demaine et al. 2007].
This partially retroactive data structure has a runtime of O(log m) where m is the total number
of updates made to the data structure since the beginning of time [Demaine et al. 2007]. For fully
retroactive priority queues, we have to consider queries into the past and this makes the data structure
more complex.
3. FULLY RETROACTIVE PRIORITY QUEUES
In this section, we introduce an implementation of the fully retroactive priority queue. Our fully
retroactive priority queue implementation is maintained as a balanced binary tree. In our implementation, we use a scapegoat tree. A scapegoat tree supports the following operations in O(log n)
amortized time where n is the number of nodes in the tree.
— SEARCH(i): returns the node with the key i.
— INSERT(i, x): inserts a node x into the tree with the key i.
— DELETE(i): deletes the node with the key i.
Scapegoat trees are a type of balanced binary tree which preserve height balance by guaranteeing
weight balance at each node. Suppose that a scapegoat tree has some parameter 0.5 < α < 1 associated with it, and suppose we define the function size(x) for some node x to be the number of nodes
in the subtree rooted at x. Then, a scapegoat tree will preserve weight balance by guaranteeing that
for each node x with children r and l, size(r) ≤ α size(x) and size(l) ≤ α size(x). Guaranteeing
weight balance at each node also guarantees that the tree will have O(log n) height; intuitively, this
is because every time one descends down a parent to child pointer, the number of descendants of
9
the node is at most decrease by a factor of α. In our implementation, we choose α = 10
. A more
detailed explanation of the tree itself can be found in [Galperin and Rivest 1993].
Scapegoat trees preserve weight balance at each node by waiting until a node x violates the weight
balance guarantee, and then rebuilding the entire subtree rooted at x. If x had k descendants and
1
started out balanced, it would take 1−α
k ± O(1) insertions into the subtree rooted at x to unbalance
1
it. Because 1−α is a constant, O(k) insertions into a subtree of size k are necessary before a rebalance
[Galperin and Rivest 1993].
At the leaves of the scapegoat tree we use to build our fully retroactive priority queue, we store
all updates to the queue across time, with the leaves sorted from left to right according to time. At
A:4
each non-leaf node x, we store a single partially retroactive priority queue that tracks all updates to
the subtree rooted at x.
Before continuing, it is important to note that a fully retroactive priority queue can be built from
scratch, given k updates, in O(k log k) time. We see later that this proof is essential in order to show
the runtime of updates when rebalancing the scapegoat tree is necessary.
L EMMA 3.1. A fully retroactive priority queue can be built containing k given updates in
O(k log k) time.
P ROOF. This is done by the following iterative process. To build a partially retroactive priority
queue, we must construct the structures given in Demaine et. al. [Demaine et al. 2007]. For the
purposes of merging, we sort the updates by time with a runtime of O(k log k). Also for the purposes
of merging, we maintain a sorted list by value of the elements in Qnow that survive to the end of the
timeline, and another storing the elements Qdel that do not. Given this augmentation, if we have two
partially retroactive priority queues Q1 and Q2 , with sorted lists of elements that are deleted and
survivors Q1,now , Q1,del , Q2,now , and Q2,del , we can create a new partially retroactive priority queue
Q3 with sorted list of survivors Q3,now = Q2,now ∪ (max|Q1,now | (Q1,now ∪ Q2,del )) (in other words,
Q3,now contains the elements from Q2,now along with the top |Q1,now | elements from Q1,now ∪ Q2,del ),
and sorted list of deleted elements Q3,del = Q1,del ∪ (max|Q2,del | (Q2,del ∪ Q1,now )).
Thus, we start with the k updates as leaves of a complete binary merge tree. Above each leaf
is a partially retroactive priority queue with one element. At each inner node of the tree, we
combine the partially retroactive priority queues Q1 and Q2 at the two nodes below by finding the max|Q1,now | Q1,now ∪ Q2,del elements in Q1 ∪ Q2 in O(k) time (just pop |Q1,now | elements
off the ends of Q1,now and Q2,del ). Let Q1,now,+ and Q2,del,+ be the sorted lists of elements of
Q1,now and Q2,del that were large enough to make the cut, respectively. We then merge the sorted
lists Q1,now,+ , Q2,del,+ , and Q2,now in O(k) time to get Q3,now , the list of survivors in the combined partially retroactive priority queue. We find Q3,del via the same mechanism, instead merging
Q1,del , Q1,now,− , and Q2,del,− .
The merging step takes O(k) time for each level because at each level the total number of updates
being merged is k. There are O(log k) levels to the binary merge tree, yielding a total time cost
of O(k log k) to create the binary merge tree. At each node of the tree, we can go back and build
the other necessary structures mentioned in [Demaine et al. 2007] in time linear to the number of
elements given the elements sorted by value and time. By an analogous argument, the number of
elements in each level is k and there are O(log k) levels so that total runtime of building all the
partially retroactive priority queue structures is O(k log k). In this step, we also make the sorted list
of values in Qnow and Qdel binary search trees for ease of fully retroactive queries later on. The
binary merge tree itself constitutes a fully retroactive priority queue; it’s a binary tree satisfying the
weight-balanced property of scapegoat trees, with a correctly initialized partially retroactive priority
queue stored at each node.
4. UPDATES
We will first consider updates to the fully retroactive priority queue. Recall that updates are inserted
into the queue with an update time. The insertion of an update is made to the queue by inserting
the given update as a leaf into the appropriate location in the balanced binary search tree and,
then, updating all nodes in the path from that leaf to the root of the tree. We create a new partially
retroactive priority queue directly above the update to hold that update alone; see Figure 7.
Deletions of updates from the priority queue use a very similar mechanism to that used by the
insertion of updates. We find the location of the update in the binary tree using its time as its key,
and then we delete it from the tree, changing all the partially retroactive priority queues in the path
to the root to not include that update.
A:5
Fig. 5: (a)
Fig. 6: (b)
Fig. 7: Update operation on a fully retroactive priority queue. The circle nodes each contain one
partially retroactive priority queue. The square leaves each contain a single update. The red path
denotes the path taken by calling Insert on an update.
Now, we will explain precisely how an update procedure works. A fully retroactive priority
queue evaluates update calls using the following procedures:
I NSERT-O P(o,t):
(1) A new node x is inserted into a fully retroactive priority queue, F, with key t.
(2) If x is not the only node in the tree (in which case it has no parent) we find its parent p (let p
have key t p and contain a marker for operation o p ), and we create a new node x0 with key equal
to min(t,t p ) + ε, where we let ε equal some tiny value. We make x0 the parent of x and p.
(3) We attach a marker of the priority queue operation o to x.
(4) We initialize a partially retroactive priority queue at both x and x0 .
(5) We call I NSERT-O P(o,t) on the partially retroactive priority queue at x, and we call I NSERTO P(o,t) and I NSERT-O P(o p ,t p ) on the partially retroactive priority queue at x0 .
(6) We call I NSERT-O P(o,t) on all partially retroactive priority queues in nodes that are ancestors of
x.
(7) If a node u is unbalanced, we rebuild the entire subtree rooted at u so that that subtree is as
balanced as possible. We fill all the nodes in the subtree rooted at u with correctly initialized
partially retroactive priority queues, using the method described in Lemma Lemma 3.1.
Figure 11 shows an example of I NSERT-O P(o4 , 2) into a fully retroactive priority queue. Step 7
of the above does not occur with every call. It only occurs if the scapegoat tree becomes unbalanced
9
with an α < 10
.
D ELETE -O P(o, t) is implemented according to a sequence of steps very similar to that of insert.
D ELETE -O P(o, t):
(1) Let node x be the priority queue of size 1 that contains the leaf with our desired operation, o, and
time, t. Let p be its parent (ie. the priority queue of size 1 that contains x). Let x0 be the other
child of p. Let g be the parent of p. We delete x and p.
(2) We connect x0 to g.
A:6
Fig. 8: (a)
Fig. 9: (b)
Fig. 10: (c)
Fig. 11: Example I NSERT-O P(o4 , 2) call on a fully retroactive priority queue. The figure shows steps
1-6 of the insertion process. Highlighted in red are changes to the queue. Ovals represent calls to the
queue and squares represent partially retroactive priority queues that contain and reflect the changes
made by the calls (ie. o1 , o2 , ...) listed.
(3) We call D ELETE -O P(o, t) on all partially retroactive priority queues in nodes that are ancestors
of x0 .
(4) Anywhere in the tree, if a node u is unbalanced, we rebuild the entire subtree rooted at u. We
fill all the nodes in the subtree rooted at u with correctly initialized partially retroactive priority
queues using the method described in Lemma Lemma 3.1.
Now, we must analyze the runtime of any update operation. For an I NSERT-O P(o, t) operation,
we incur the cost of inserting into the scapegoat tree which takes O(log m) amortized time with an
increase in potential of O(1). For a D ELETE -O P(o, t) operation, we incur a runtime of O(log m) for
deleting from the tree. The creation of a new partially retroactive priority queue takes O(1) time and
the deletion of two nodes also take O(1) time. The bottleneck of the operation occurs when we have
to go back and insert or delete the update in every partially retroactive priority queue in its search
path. Inserting or deleting an update in a partially retroactive priority queue takes O(log m) time.
A:7
We have potentially O(log m) partially retroactive priority queues to insert into or delete from. The
total runtime is then O(log2 m).
What we have to be careful about is the runtime of rebuilding the fully retroactive priority queue
or a subtree of it. We potentially have to rebuild a tree with O(m) updates. By Lemma Lemma 3.1,
we see that given m updates, we can rebuild a tree in O(m log m) time. When we rebuild a tree
m
during an update operation, we know we have already incurred a potential of at least 10
nodes
which were inserted or deleted without rebuilding the tree. Using aggregate analysis, we can show
that the amortized runtime for a rebuild of the tree is O(log m), which falls within our time bound
allotted for an update operation.
Therefore, the time of an update is O(log2 m).
5. QUERIES
In this section, we explain how to query the fully retroactive priority queue.
For a given retroactive priority queue Q, recall that we have used Qnow to be the set of elements
that survive until the end of the data structure’s timeline and Qdel to be the complement of Qnow ;
that is, Qdel is the set of elements that were inserted but then deleted before the present time.
Remember that for a fully retroactive priority queue, the only query operation we can perform
is F IND -M IN(t). We first define a few more terms to be used in our proof of the runtime of this
operation. Then, we will present the procedure for the operation and prove its runtime.
retroactive
priority queue, QM, oforder k consist of two lists, Lnow =
Let a mergeable partially
Q1,now , Q2,now , . . . , Qk,now and Ldel = Q1,del , Q2,del , . . . , Qk,del each containing k balanced binary
search trees with subtree sum augmentation. Therefore, ∪ki=1 Qi,now = Lnow and ∪ki=1 Qi,del = Ldel .
Note that a partially retroactive priority queue can be converted to a mergeable partially retroactive
priority queue of order 1 in constant time.
We will show later how to perform the merge between two mergeable partially retroactive priority
queues. For now, we assume the existence of such a procedure to introduce F IND -M IN(t).
F IND -M IN(t):
(1) Obtain a set of partially retroactive priority queues from the fully retroactive priority queue such
that the list of updates span (−∞,t]. To do this, we perform a search for t in the tree and take all
the queues present in the nodes just left of the search path. See Figure 12.
(2) Produce mergeable partially retroactive priority queues from this set.
(3) Merge the queues by constructing a balanced binary tree with the mergeable queues as the leaves
of the tree. This merge tree has height O(log log m). See Figure 13.
(4) Query each tree of Lnow to find the minimum value of each tree.
(5) The minimum out of all the minimum values obtained from queries in Step 4 is the result of
F IND -M IN(t).
We can perform the merge in a similar way to the merging that was done in Lemma 3.1. We
want to merge two mergeable partially retroactive priority queues into one QM. Given two mergeable partially retroactive priority queues, QM1 and QM2 , we find the maximum |L1,now | elements
in L1,now ∪ L2,del . We split the trees containing the elements and add the split-trees and L2,now into
Lnow . Then, similarly, to find the deleted elements, we find the minimum |L2,del | in L1,now ∪ L2,del
and append these split-trees and L1,del into Ldel . Therefore, the difference between this merge procedure and Lemma 3.1 is that instead of actually merging the two lists, we place trees containing the
elements into Lnow and Ldel . As in Lemma 3.1, we can see that this presents an accurate representation of the surviving and deleted elements because we are making the number of deleted elements
equal the number of DELETE - MIN operations (within a time period) and the lowest such elements
are put into Ldel .
However, this procedure may potentially cause a problem because we could have O(m) trees to
query. We will introduce a merging procedure later on in this section that solves this problem.
A:8
Fig. 12: (a)
Fig. 13: (b)
Fig. 14: To perform a F IND -M IN(t), we follow the path from root to the leaf update at t to obtain
a set of priority queues to merge. Here, we perform F IND -M IN(5). The blue nodes contain queues
that will be converted to mergeable queues and merged to account for all updates in the range
t ∈ (−∞, 5]. The merge is done by forming a binary merge tree with each priority queue as a leaf.
We will now describe the process of creating Lnow and Ldel in more detail. Assume that QM1
and QM2 are both of order k. The means that |Lnow | = k and |Ldel | = k for both mergeable partially
retroactive priority queues. We define a split-key as the element y in the 2k trees in L2,del and L1,now
such that there are |L1,now | update elements in the trees that are larger than it. After this split-key y
is obtained, we can query each of the 2k trees given by L1,now and L2,del and split. The set of split
trees can then be added to the set of trees in L2,now to conclude the merge.
The procedure for finding Ldel for the merged queue is analogous.
Because each partially retroactive priority queue is augmented with a balanced binary search tree
with subtree sum [Demaine et al. 2007], to find y, we use a modified version of the median finding
algorithm explained in [Frederickson and Johnson 1982].
Given 2k balanced binary search trees, we weigh each root by the size of their corresponding
subtree plus 1 in O(k) time. Then, we find the median, w, of the root weights. We will denote S as
the sum of w and the weights of all roots with weights larger than w. If S is greater than |QM1,now |,
then eliminate the left halves (subtree and root) of all trees whose root weights are less than or equal
to w. If S is less than |QM1,now |, then similarly eliminate all right halves of trees whose roots are
greater than or equal to w. If S is equal to |QM1,now |, then w is the split key (if w is the average of
two values, then the the split-key is the greater value).
If the previous step took away the right subtrees, update |QM1,now | to equal |QM1,now | − E where
E was the number of elements we eliminated when we took away the right subtrees and roots. Repeat
the process on the new trees until we have found the split key. Given 2k balanced search trees, the
process of finding y takes O(k log m) time as proven in [Frederickson and Johnson 1982] because
O(m/4) elements are eliminated at each iteration and O(k) work is done at each step. Querying for
the split-key in each of 2k trees and performing the split also takes O(k log m) time.
This results in the newly merged QM with order 3k. We will now show that by cleverly concatenating split trees, we can reduce the order to approximately 2k.
We assume that i = 0 at the level of the leaves. For each level where i > 0, we will show that the
order of each mergeable partially retroactive priority queue is at most (2i+1 − 1)k.
A:9
6. MERGING
L EMMA 6.1. Given 2 mergeable priority queues QM1 and QM2 each of order k, let TG be the
set of 2k trees obtained from splitting L1,now and L2,del to find the k largest elements for Lnow . Let TL
be the set of 2k trees obtained from splitting L1,now and L2,del to obtain the smallest k elements for
Ldel . Every element of every tree in TG will be greater than or equal to every element of every tree
in TL .
P ROOF. We prove by contradiction. We assume there exists one element, a, in TL that is greater
than some element, b, in TG . Let y be the split key. Then, because a is in TL , a must be less or equal
to y. Since b is in TG , b must be greater than or equal to y. Therefore, a ≤ y ≤ b and a ≤ b. We’ve
reached a contradiction.
L EMMA 6.2. Given a binary merge tree of total height n with leaves containing QM’s, each of
order k. The height, i, at the leaves is i = 0. The order of each QM at level i (where i > 0) is at most
(2i+1 − 1)k.
P ROOF. We prove this by induction. We first introduce some conventions. Let QM i be a mergel−1
l−1
l
able partially retroactive priority queue at level i. Let L1,2,now
be the merged L1,now
and L1,del
of
l−1
l−1
l−1
l−1
l+1
l
QM1 and QM2 , respectively. Similarly let L1,2,del be the merged L1,del and L2,del . Let L1,2,3,4,now
l and QM l and so on.
be the merged Lnow of QM1,2
3,4
To prove the base case of i = 1, assume that QM10 and QM20 are two mergeable queues at i = 0.
0
1
0
and L2,del
to obtain at most 2k trees and add the 2k trees to the
To obtain L1,2,now
, we split L1,now
0
1
list of k trees in L2,now . Then, L1,2,now contains 3k trees. We can repeat the process symmetrically
1
1 has order 3k at level i = 1. We now assume that for levels i ≤ n − 1,
for QM1,2,del
. Therefore, QM1,2
i
i+1
the order of each QM is (2 − 1)k. We will use this assumption to prove the case when i = n.
n−1
n−1
be 2 mergeable queues at level i = n − 1. Then, by our assumption, to
and QM3,4
Let QM1,2
n−1
n−1
n
, and
, (2n − 1)k split trees from L1,2,now
form L1,2,3,4,now we obtain (2n − 1)k trees from L3,4,now
n−1
n
n
n
(2 − 1)k split trees from L3,4,del . Therefore, L1,2,3,4,now now has size 3(2 − 1)k.
n
Some of the trees introduced to QM1,2,3,4,now
may be merged with other trees in the list to reduce
n
n
n+1
QM1,2,3,4 ’s order from 3(2 − 1)k to (2
− 1)k.
Figure ?? illustrates the merge procedure of the mergeable queues’ trees. Using our proof of
Lemma 6.1, we can show as follows that 2(2n−1 − 1)k such trees can be merged, thus reducing the
order to (3(2n − 1)) − (2(2n−1 − 1))k = (2n+1 − 1)k.
n−1
n−1
n−1
n
, and L3,4,del
.
, L1,2,now
We create L1,2,3,4,now
from the combined list of trees obtained from L3,4,now
n−2
n−2
n−1
Using the terminology introduced in Lemma 6.1, L3,4,now is composed of L4,now and LG,3,now,4,del .
n−1
n−2
n−2
n−2
Also, L3,4,del
is composed of L3,del
and TL,3,now,4,del
. By Lemma 6.1, all elements in TL,3,now,4,del
is
n−2
n
n−1
less than or equal to all elements in TG,3,now,4,del . Recall that L1,2,3,4,now contains 2(2
− 1)k trees
n−2
n−2
n−1
from TG,3,now,4,del and 2(2
− 1)k trees from TL,3,now,4,del . Thus, we can concatenate pairs of the
4(2n−1 − 1)k trees, reducing the number of trees by 2(2n−1 − 1)k, in O(k log m) time given that one
tree is completely composed of elements that are less than the tree it is concatenated with. Therefore,
n
the number of trees in T1,2,3,4,now
can be reduced by 2(2n−1 − 1)k through tree concatenation. A
n
symmetric argument holds for T1,2,3,4,del , making the order of trees at level i = n, (2n+1 − 1)k.
Without loss of generality, Lemma 6.2 holds for mergeable partially retroactive priority queues
of any size. k would just be the largest size of any queue.
A merger at a particular level, i, produces at most (2i+1 + 1)k trees by our proof of Lemma 6.2.
Therefore, using our previously mentioned merge procedure, one merge takes (2i+1 +1)k log m time.
A:10
The total number of mergers at level i is log2im . Thus, the merger across the entire tree takes time
Plog log m (2i+1 +1)
k log2 m = k log2 m(2 log log m + log1 m − 2) = O(log2 m log log m). For constant k,
i=0
2i
the time needed to do a merge for a query is O(log2 m log log m). Once the merged partially retroactive priority queue is obtained, we can perform F IND -M IN(t) in time O(log2 m) on this final merged
queue because there are (2log log m+1 + 1)k trees and each tree takes O(log m) time to find its minimum element. Therefore, the total time necessary to perform F IND -M IN(t) is O(log2 m log log m).
7. ADDITIONAL FUNCTIONS
There is one more addition to this data structure that we did not mention in our previous section.
— DELETE(k): This is a possible call to the priority queue itself, and it can be inserted or removed
from the retroactive priority queue as normal. (In other words, I NSERT-O P(delete(k), t) will
insert the call into the timeline at time t.)
First, DELETE(k) searches the priority queue for an element with key equal to k, and assuming
such an element exists in the queue, deletes it from the queue. If there are several elements in the
data structure with the same key, correct behavior is not guaranteed. If no such element exists in the
data structure, the call to DELETE(k) does nothing.
Fully retroactive priority queues can be easily modified to include it as follows. For each node
in the binary tree representation of the fully retroactive priority queue, we can augment that node
with two balanced binary search trees, such as AVL trees. Each element of each AVL tree contains
a pointer to the corresponding element in either Qnow (for the first AVL tree) or Qdel (for the second
AVL tree). On a DELETE(k) call, we first search the AVL tree that contains pointers to Qnow ; if there
exists one in the tree, we delete it, we follow the pointer to Qnow , and we delete the element from
Qnow . (Note that we do not add the element into Qdel ; the element is completely excised from the
data structure.) If there exists no element with key k in Qnow , then we search the AVL tree that points
to Qdel for the element, and follow the pointer to Qdel and delete both elements. If the element is
neither in Qnow nor Qdel , we terminate without doing anything.
We maintain the AVL trees in the same way Qnow and Qdel are maintained; whenever an element is
deleted or inserted as a result of the deletion or insertion of some other call to the priority queue, we
add or delete elements correspondingly into the appropriate AVL tree. In particular, we make sure
that the AVL trees always contain the same number of elements as Qnow and Qdel respectively; i.e.,
when a DELETE - MIN call moves an element from Qnow to Qdel , we find the corresponding element
in the first AVL tree and move it to the second. Moreover, when a DELETE(k) call is itself deleted,
we re-insert k into either Qnow or Qdel , whichever it was in before, and into the corresponding AVL
tree. (We can easily store the information of where the element was when it was deleted in the fully
retroactive priority queue node containing the DELETE(k) call.)
Because searching balanced binary search trees takes at most O(log m) time, we incur a cost
of O(log m) when we perform DELETE(k) on a partially priority queue. We potentially perform DELETE(k) on O(log m) partially retroactive queue. Therefore, the runtime of any I NSERTO P(DELETE(k), t) or D ELETE -O P(DELETE(k), t) is O(log2 m).
8. MONOIDS
We define a monoid to be a type of group which contains an identity element I and an associative
function that acts on any two elements in the elements and results in another element in the group. In
other words, suppose that S is group and f (s1 , s2 ) is an operation on any two elements in the group
and if s1 ∈ S and s2 ∈ S, then f (s1 , s2 ) ∈ S. S is a monoid if it is subject to the following constraints
(1) f (I, x) = f (x, I) = x∀x ∈ S
(2) f ( f (s1 , s2 ), s3 ) = f (s1 , f (s2 , s3 ))∀s1 , s2 , s3 ∈ S.
A:11
One example of a monoid might be the integers, where the identity element would be 0 and
the addition operation would be the associative function. An example more topical to this paper
would be the partially retroactive priority queue; in this case, the identity element would be the
empty partially retroactive priority queue and the associative function is the given merge function.
Merging two mergeable partially retroactive priority queues presents another element in the group
and is associative (we can merge the priority queues in any associative order).
We will show that if any partially retroactive data structure may be formulated as a monoid group
with a merge operation that can allow accurate queries, we can obtain a fully retroactive version of
the structure with O(T (m) log m + Q(m)) query time and O(A(m) log m) update time where T (m),
Q(m), and A(m) represent the merge, query, and update time in the original partially retroactive data
structure.
L EMMA 8.1. Given a partially retroactive data structure that can be formulated as a monoid
with a merge operation and allows accurate queries, we may transform the partially retroactive
data structure to a fully retroactive data structure with O(T (m) log m + Q(m)) query time and
O(A(m) log m) update time.
P ROOF.
We can build a data structure similar to our fully retroactive priority queue. Let F be the fully
retroactive data structure of D, a partially retroactive data structure with a monoid version MD and a
merge function fM (x, y). We create a binary search tree containing all updates to the fully retroactive
data structure as leaves ordered by time. Each non-leaf node, x, contains a version of D that reflects
all updates in the leaves of the subtree rooted at x. To perform an I NSERT-O P(o, t) or D ELETE -O P(o,
t), we binary search for the correct position in the tree to insert the update. We perform the update
on every node in the search path. To perform a Query(t), we merge the D’s that corresponding to
the range (−∞,t] and query the merged structure.
Because we update each D along the path of insertion of an update, each D contains an accurate
version of the partially retroactive data structure spanning a range of time. Let A(m) be the time of
an update for D. Then, to update F, we update O(log m) D’s. The total cost of an I NSERT-O P or
D ELETE -O P operation is O(A(m) log m).
To perform a query at time t, we must create a version of the data structure that spans the range
(−∞,t] and query that structure. We may obtain this version of the data structure by walking the
binary search tree to the leaf containing t and merging all D’s immediately to the left of the path.
Each structure covers a disjoint range. Suppose that T (m) is the time it takes to merge any two D’s
and Q(m) is the time it takes to query D. We can merge O(log m) D’s using the binary merge tree
presented in Lemma 6.2 in time O(M(m) log m) time. (If we have more information about M(m),
we can potentially reduce the runtime to O(M(m) log log m).) Querying this structure then takes
O(Q(m)) time. Therefore, the total runtime of Query(t) is O(M log m + Q(m)).
9. CONCLUSION
This data structure constitutes one of the only nontrivial results in full retroactivity. Other results in
full retroactivity at the time of writing include:
(1) A fully retroactive queue with all retroactive operations taking O(log m) time. [Demaine et al.
2007]
(2) A fully retroactive deque with all retroactive operations taking O(log m) time. [Demaine et al.
2007]
(3) A fully retroactive union-find data structure with all retroactive operations taking O(log m)
time. [Demaine et al. 2007]
(4) A fully retroactive dictionary with all retroactive operations taking O(log m) time. [Demaine
et al. 2007]
(5) A fully retroactive stack with all retroactive operations taking O(log m) time. [Demaine et al.
2007]
A:12
(6) A fully retroactive data structure supporting predecessor and successor operations in O(log m)
time. [Giora and Kaplan 2009].
√
The past best known performance for a fully retroactive priority queue was O( m log m) time
for retroactive operations. This solution used the partially retroactive priority queue described in
the 2007 paper by Demaine et al. [Demaine et al. 2007], and applied to it a general transformation
which took partially retroactive data structures and made them fully retroactive. This transformation
was described in that same paper. [Demaine et al. 2007]
By improving the performance of fully retroactive priority queues from polynomial performance
to polylogarithmic performance, we have provided a new contribution to the field of full retroactivity. Additionally, priority queues are arguably the most complicated data structures to have had full
retroactivity applied to them at the time of writing.
10. DISCUSSION
There are still some open questions regarding this structure. We think that the data structure
can be improved further. In particular, we conjecture that query time can be reduced from
O(log2 m log log m) to O(log2 m), making queries as fast as updates. Additionally, we think it is
possible that the space used by the data structure can be reduced from O(m log m) to O(m).
We also believe that this data structure may have potential applications in computational geometric structures. The natural model represented by fully retroactive priority queues is a ray shooting
diagram that maintains the history of all vertical ray shoots and updates all ray shoots if another
vertical ray is added. The only limitation in our model is that the vertical rays must originate from
−∞. We think that a potentially interesting addition to our structure is allowing deletions of the k-th
smallest element.
It is clear to the authors of this paper that in the field of full retroactivity, there is still much more
exciting research to be done.
REFERENCES
E. D. Demaine, J. Iacono, and S. Langerman. Retroactive data structures. ACM Transactions on
Algorithms (TALG), 3(2):13, 2007.
G. N. Frederickson and D. B. Johnson. The complexity of selection and ranking in< i> x</i>+<
i> y</i> and matrices with sorted columns. Journal of Computer and System Sciences, 24(2):
197–208, 1982.
I. Galperin and R. L. Rivest. Scapegoat trees. In Proceedings of the fourth annual ACM-SIAM Symposium on Discrete algorithms, pages 165–174. Society for Industrial and Applied Mathematics,
1993.
Y. Giora and H. Kaplan. Optimal dynamic vertical ray shooting in rectilinear planar subdivisions.
ACM Transactions on Algorithms (TALG), 5(3):28, 2009.