* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Download A Fully Retroactive Priority Queues
Survey
Document related concepts
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.