data_structures_rmolinari 0.2.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +33 -0
- data/lib/data_structures_rmolinari/disjoint_union.rb +30 -14
- data/lib/data_structures_rmolinari/generic_segment_tree.rb +8 -8
- data/lib/data_structures_rmolinari/heap.rb +64 -42
- data/lib/data_structures_rmolinari/max_priority_search_tree.rb +70 -119
- data/lib/data_structures_rmolinari/shared.rb +9 -1
- data/lib/data_structures_rmolinari.rb +17 -14
- metadata +3 -3
- data/lib/data_structures_rmolinari/minmax_priority_search_tree.rb +0 -670
@@ -1,670 +0,0 @@
|
|
1
|
-
require 'must_be'
|
2
|
-
|
3
|
-
require_relative 'shared'
|
4
|
-
|
5
|
-
# A priority search tree (PST) stores points in two dimensions (x,y) and can efficiently answer certain questions about the set of
|
6
|
-
# point.
|
7
|
-
#
|
8
|
-
# The structure was introduced by McCreight [1].
|
9
|
-
#
|
10
|
-
# It is a binary search tree which is a max-heap by the y-coordinate, and, for a non-leaf node N storing (x, y), all the nodes in
|
11
|
-
# the left subtree of N have smaller x values than any of the nodes in the right subtree of N. Note, though, that the x-value at N
|
12
|
-
# has no particular property relative to the x values in its subtree. It is thus _almost_ a binary search tree in the x coordinate.
|
13
|
-
#
|
14
|
-
# See more: https://en.wikipedia.org/wiki/Priority_search_tree
|
15
|
-
#
|
16
|
-
# It is possible to build such a tree in place, given an array of pairs. See [2]. In a follow-up paper, [3], the authors show how to
|
17
|
-
# construct a more flexible data structure,
|
18
|
-
#
|
19
|
-
# "[T]he Min-Max Priority Search tree for a set P of n points in R^2. It is a binary tree T with the following properties:
|
20
|
-
#
|
21
|
-
# * For each internal node u, all points in the left subtree of u have an x-coordinate which is less than the x-coordinate of any
|
22
|
-
# point in the right subtree of u.
|
23
|
-
# * The y-coordinate values of the nodes on even (resp. odd) levels are smaller (resp. greater) than the y-coordinate values of
|
24
|
-
# their descendants (if any), where the root is at level zero.
|
25
|
-
#
|
26
|
-
# "The first property implies that T is a binary search three on the x-coordinates of the points in P, excepts that there is no
|
27
|
-
# relation between the x-coordinates of the points stored at u and any of its children. The second property implies that T is a
|
28
|
-
# min-max heap on the y-coordinates of the points in P."
|
29
|
-
#
|
30
|
-
# I started implementing the in-place PST. Then, finding the follow-up paper [3], decided to do that one instead, as the paper says
|
31
|
-
# it is more flexible. The point is to learn a new data structure and its associated algorithms.
|
32
|
-
#
|
33
|
-
# The algorithms are rather bewildering. Highest3SidedUp is complicated, and only two of the functions CheckLeft, CheckLeftIn,
|
34
|
-
# CheckRight, CheckRightIn are given; the other two are "symmetric". But it's not really clear what the first are actually doing, so
|
35
|
-
# it's hard to know what the others actually do.
|
36
|
-
#
|
37
|
-
# The implementation is incomplete. The pseduo-code in the paper is buggy (see the code below), which makes progress difficult.
|
38
|
-
#
|
39
|
-
# [1] E. McCreight, _Priority Search Trees_, SIAM J. Computing, v14, no 3, May 1985, pp 257-276.
|
40
|
-
# [2] De, Maheshwari, Nandy, Smid, _An in-place priority search tree_, 23rd Annual Canadian Conference on Computational Geometry.
|
41
|
-
# [3] De, Maheshwari, Nandy, Smid, _An in-place min-max priority search tree_, Computational Geometry, v46 (2013), pp 310-327.
|
42
|
-
# [4] Atkinson, Sack, Santoro, Strothotte, _Min-max heaps and generalized priority queues_, Commun. ACM 29 (10) (1986), pp 996-1000.
|
43
|
-
class DataStructuresRMolinari::MinmaxPrioritySearchTree
|
44
|
-
include Shared
|
45
|
-
|
46
|
-
# The array of pairs is turned into a minmax PST in-place without cloning. So clone before passing it in, if you care.
|
47
|
-
#
|
48
|
-
# Each element must respond to #x and #y. Use Pair (above) if you like.
|
49
|
-
def initialize(data, verify: false)
|
50
|
-
@data = data
|
51
|
-
@size = @data.size
|
52
|
-
|
53
|
-
construct_pst
|
54
|
-
return unless verify
|
55
|
-
|
56
|
-
# puts "Validating tree structure..."
|
57
|
-
verify_properties
|
58
|
-
end
|
59
|
-
|
60
|
-
# Let Q = [x0, infty) X [y0, infty) be the northeast "quadrant" defined by the point (x0, y0) and let P be the points in this data
|
61
|
-
# structure. Define p* as
|
62
|
-
#
|
63
|
-
# - (infty, infty) if Q \intersect P is empty and
|
64
|
-
# - the leftmost (i.e., min-x) point in Q \intersect P otherwise
|
65
|
-
#
|
66
|
-
# This method returns p*.
|
67
|
-
#
|
68
|
-
# From De et al:
|
69
|
-
#
|
70
|
-
# [t]he variables best, p, and q satisfy the folling invariant:
|
71
|
-
#
|
72
|
-
# - if Q \intersect P is nonempty then p* \in {best} \union T(p) \union T(q)
|
73
|
-
# - if Q \intersect P is empty then p* = best
|
74
|
-
# - p and q are at the same level of T and x(p) <= x(q)
|
75
|
-
#
|
76
|
-
# Here T(x) is the subtree rooted at x
|
77
|
-
def leftmost_ne(x0, y0)
|
78
|
-
best = Pair.new(INFINITY, INFINITY)
|
79
|
-
p = q = root
|
80
|
-
|
81
|
-
in_q = ->(pair) { pair.x >= x0 && pair.y >= y0 }
|
82
|
-
|
83
|
-
# From the paper:
|
84
|
-
#
|
85
|
-
# takes as input a point t \in P and updates best as follows: if t \in Q and x(t) < x(best) then it assignes best = t
|
86
|
-
#
|
87
|
-
# Note that the paper identifies a node in the tree with its value. We need to grab the correct node.
|
88
|
-
update_leftmost = lambda do |node|
|
89
|
-
t = val_at(node)
|
90
|
-
if in_q.call(t) && t.x < best.x
|
91
|
-
best = t
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
# Generalize the c1,...,c4 idea from the paper in line with the BUG 2 IN PAPER notes, below.
|
96
|
-
#
|
97
|
-
# Given: 0 or more nodes n1, ..., nk in the tree. All are at the same level, which is a "max level" in our MinmaxPST, such that
|
98
|
-
# x(n1) <= x(n2) <= ... <= x(nk). (Note: it is expected that the nj are either children or grandchildren of p and q, though we
|
99
|
-
# don't check that.)
|
100
|
-
#
|
101
|
-
# If k = 0 return nil. Otherwise...
|
102
|
-
#
|
103
|
-
# We return two values p_goal, q_goal (possibly equal) from among the nj such that
|
104
|
-
#
|
105
|
-
# - p_goal is not to the right of q_goal in the tree and so, in particular x(p_goal) <= x(q_goal)
|
106
|
-
# - if and when the auction reaches p = p_goal and q = q_goal the algorithm invariant will be satisfied.
|
107
|
-
#
|
108
|
-
# As a special case, we return nil if we detect that none of the subtrees T(nj) contain any points in Q. This is a sign to
|
109
|
-
# terminate the algorithm.
|
110
|
-
#
|
111
|
-
# See the notes at "BUG 2 IN PAPER" below for more details about what is going on.
|
112
|
-
determine_goal_nodes = lambda do |nodes|
|
113
|
-
node_count = nodes.size
|
114
|
-
return nil if node_count.zero?
|
115
|
-
|
116
|
-
if val_at(nodes.last).x <= x0
|
117
|
-
# Only the rightmost subtree can possibly have anything Q, assuming that all the x-values are distinct.
|
118
|
-
return [nodes.last, nodes.last]
|
119
|
-
end
|
120
|
-
|
121
|
-
if val_at(nodes.first).x > x0
|
122
|
-
# All subtrees have x-values large enough to provide elements of Q. Since we are at a max-level the y-values help us work
|
123
|
-
# out which subtree to focus on.
|
124
|
-
leftmost = nodes.find { |node| val_at(node).y >= y0 }
|
125
|
-
|
126
|
-
return nil unless leftmost # nothing left to find
|
127
|
-
|
128
|
-
# Otherwise we explore the leftmost subtree. Its root is in Q and can't be beaten by anything to its right.
|
129
|
-
return [leftmost, leftmost]
|
130
|
-
end
|
131
|
-
|
132
|
-
values = nodes.map { |n| val_at(n) }
|
133
|
-
|
134
|
-
# Otherwise x(n1) <= x0 < x(nk). Thus i is well-defined.
|
135
|
-
i = (0...node_count).select { |j| values[j].x <= x0 && x0 < values[j + 1].x }.min
|
136
|
-
|
137
|
-
# these nodes all have large-enough x-values and so this finds the ones in the set Q.
|
138
|
-
new_q = nodes[(i + 1)..].select { |node| val_at(node).y >= y0 }.min # could be nil
|
139
|
-
new_p = nodes[i] if values[i].y >= y0 # The leftmost subtree is worth exploring if the y-value is big enough. Otherwise not
|
140
|
-
new_p ||= new_q # if nodes[i] is no good we send p along with q
|
141
|
-
new_q ||= new_p # but if there was no worthwhile value for q we should send it along with p
|
142
|
-
|
143
|
-
return nil unless new_p
|
144
|
-
|
145
|
-
[new_p, new_q]
|
146
|
-
end
|
147
|
-
|
148
|
-
until leaf?(p)
|
149
|
-
level = Math.log2(p).floor # TODO: don't calculate log every time!
|
150
|
-
|
151
|
-
update_leftmost.call(p)
|
152
|
-
update_leftmost.call(q)
|
153
|
-
|
154
|
-
if p == q
|
155
|
-
if one_child?(p)
|
156
|
-
p = q = left(p)
|
157
|
-
else
|
158
|
-
q = right(p)
|
159
|
-
p = left(p)
|
160
|
-
end
|
161
|
-
else
|
162
|
-
# p != q
|
163
|
-
if leaf?(q)
|
164
|
-
q = p # p itself is just one layer above the leaves, or is itself a leaf
|
165
|
-
elsif one_child?(q)
|
166
|
-
# Note that p has two children
|
167
|
-
if val_at(left(q)).x < x0
|
168
|
-
# x-values below p are too small
|
169
|
-
p = q = left(q)
|
170
|
-
elsif val_at(right(p)).x <= x0
|
171
|
-
# x-values in T(right(p)) are too small. DISTINCT-X
|
172
|
-
p = right(p)
|
173
|
-
q = left(q)
|
174
|
-
else
|
175
|
-
# BUG 1 IN PAPER.
|
176
|
-
#
|
177
|
-
# So, x(q_l) >= x0 and x(p_r) > x0. But how can we be sure that the child of q isn't the winner?. Should we be trying
|
178
|
-
# it in this case?
|
179
|
-
#
|
180
|
-
# Yes: otherwise it never gets checked.
|
181
|
-
|
182
|
-
update_leftmost.call(left(q))
|
183
|
-
q = right(p)
|
184
|
-
p = left(p)
|
185
|
-
end
|
186
|
-
else
|
187
|
-
# p and q both have two children
|
188
|
-
|
189
|
-
# BUG 2 IN PAPER.
|
190
|
-
#
|
191
|
-
# Define c as the paper does:
|
192
|
-
#
|
193
|
-
# (c1, c2, c3, c4) = (left(p), right(p), left(q), right(q))
|
194
|
-
#
|
195
|
-
# Because of the PST property on x and the invariant x(p) <= x(q) we know that
|
196
|
-
#
|
197
|
-
# x(c1) <= x(c2) <= x(c3) <= x(c4)
|
198
|
-
#
|
199
|
-
# Similarly, the sets of values x(T(ci)) are pairwise ordered in the same sense.
|
200
|
-
#
|
201
|
-
# Suppose further that x(ci) <= x0 <= x(c(i+i)). Then we know several things
|
202
|
-
#
|
203
|
-
# - there might be a "winner" (point in Q) in T(ci), perhaps ci itself.
|
204
|
-
# - there are not any winners in T(cj) for j < i, becasue the x-values there aren't big enough
|
205
|
-
# - any winner in ck, for k >= i, will be the left of and thus beat any winner in c(k+1), because of the ordering of
|
206
|
-
# x-values
|
207
|
-
#
|
208
|
-
# If x(c4) <= x0 then the rightmost subtree T(c4) is the only one worth checking and we set p = q = c4.
|
209
|
-
# If x(c1) > x0 then we take i = 0 and ignore the logic on ci in what follows and setting p = q.
|
210
|
-
#
|
211
|
-
# Pretend for the moment that we are using a MaxPST instead of a MinmaxPST. Then we can look at y values to learn more.
|
212
|
-
#
|
213
|
-
# - if y(ci) >= y0 then we need to search T(ci), so we will update p = ci
|
214
|
-
# - but if y(ci) < y0 then there are no winners in T(ci) because the y-values are too small.
|
215
|
-
# - similarly, if y(c(i+i)) >= y0 then we need to search T(c(i+1)). Indeed c(i+1) itself is in Q and beats any winner in
|
216
|
-
# subtrees further to the right
|
217
|
-
# - so, let k > i be minimal such that y(ck) >= y0, if there is any. Note that ck is itself a winner. Then
|
218
|
-
# - if y(ci) >= y0,
|
219
|
-
# - set p = ci, and q = ck (or q = ci if there is no such k)
|
220
|
-
# - otherwise (T(ci) has no winners because its y-values are too small)
|
221
|
-
# - if k is defined set p = q = ck. Otherwise HALT (there are no more winners)
|
222
|
-
#
|
223
|
-
# But we are working with a MinmaxPST rather than a MaxPST, so we have to work harder. If c1, ..., c4 (the children of p
|
224
|
-
# and q) are in a "max-level" of the tree - that is, an even level - then the logic above still applies. But if they are
|
225
|
-
# at a min level things are trickier and we need to go another layer down.
|
226
|
-
#
|
227
|
-
# The paper knows that we need to look a further layer down, but the logic is too simplistic. It looks at cj for j > i and
|
228
|
-
# checks if cj or either of its children are in Q. But that's not good enough. For the same reason that in a MaxPST we may
|
229
|
-
# need to explore below T(ci) even if ci isn't in Q, we may need to decend through one of the grandchilden of p or q even
|
230
|
-
# if that grandchild isn't in Q.
|
231
|
-
#
|
232
|
-
# Getting a bit handwavey especially over what happens near the leaves...
|
233
|
-
#
|
234
|
-
# Consider the children d1, d2, ..., dm, of ci, ..., c4 (and so grandchildren of p and q). They are at a max-level and so
|
235
|
-
# the logic described applies to the dk. If ci happens to be a winner we can set p = ci and work out what to do with q by
|
236
|
-
# looking at the children of c(i+1), ..., c4. Otherwise we look at all the dj values (up to 8 of them), apply the logic
|
237
|
-
# above to work out that we want to head for, say, p = ds and q = dt, and in this cycle update p = parent(ds), q =
|
238
|
-
# parent(dt). (We also need to submit the values c(i+1)..c4 to UpdateLeftmost.)
|
239
|
-
#
|
240
|
-
# In other words, we can use the MaxPST logic on d1,...,dm to decide where we need to go, and then step to the relevant
|
241
|
-
# parents among the cj.
|
242
|
-
|
243
|
-
c = [left(p), right(p), left(q), right(q)]
|
244
|
-
if level.odd?
|
245
|
-
# the elements of c are at an even level, and hence their y values are maxima for the subtrees. We can learn what we
|
246
|
-
# need to know from them
|
247
|
-
p, q = determine_goal_nodes.call(c)
|
248
|
-
if p && !q
|
249
|
-
# byebug
|
250
|
-
# determine_goal_nodes.call(c)
|
251
|
-
raise 'bad logic'
|
252
|
-
end
|
253
|
-
else
|
254
|
-
# They are at an odd level and so aren't helpful in working out what to do next: we look at their children, which are in
|
255
|
-
# a max-level. We need to check the elements of c against best since we are otherwise ignoring them.
|
256
|
-
c.each { |n| update_leftmost.call(n) }
|
257
|
-
|
258
|
-
d = c.map { [left(_1), right(_1)]}.flatten.select { |n| n <= @size }
|
259
|
-
|
260
|
-
# Note that we are jumping down two levels here!
|
261
|
-
p, q = determine_goal_nodes.call(d)
|
262
|
-
if p && !q
|
263
|
-
# byebug
|
264
|
-
# determine_goal_nodes.call(c)
|
265
|
-
raise 'bad logic'
|
266
|
-
end
|
267
|
-
|
268
|
-
p
|
269
|
-
end
|
270
|
-
|
271
|
-
return best unless p # nothing more to do
|
272
|
-
end
|
273
|
-
end
|
274
|
-
end
|
275
|
-
update_leftmost.call(p)
|
276
|
-
update_leftmost.call(q)
|
277
|
-
best
|
278
|
-
end
|
279
|
-
|
280
|
-
# Let Q be the "three-sided query range" [x0, x1] X [y0, infty) and let P_Q be P \intersect Q.
|
281
|
-
#
|
282
|
-
# If P_Q is empty then p* = (infty, -infty).
|
283
|
-
# Otherwise, p* is the point in P_Q with maximal y value.
|
284
|
-
#
|
285
|
-
# This method returns p*
|
286
|
-
# def highest_3_sided_up(x0, x1, y0)
|
287
|
-
# best = Pair.new(INFINITY, -INFINITY)
|
288
|
-
|
289
|
-
# in_q = lambda do |pair|
|
290
|
-
# pair.x >= x0 && pair.x <= x1 && pair.y >= y0
|
291
|
-
# end
|
292
|
-
|
293
|
-
# # From the paper:
|
294
|
-
# #
|
295
|
-
# # takes as input a point t and does the following: if t \in Q and y(t) > y(best) then it assignes best = t
|
296
|
-
# #
|
297
|
-
# # Note that the paper identifies a node in the tree with its value. We need to grab the correct node.
|
298
|
-
# #
|
299
|
-
# # The algorithm is complicated. From the paper:
|
300
|
-
# #
|
301
|
-
# # Since Q is bounded by two vertical sides, we use four index variables p, p', q and q' to guide the search path. In addition,
|
302
|
-
# # we use four bits L, L', R and R'; these correspond to the subtrees of T rooted at the nodes p, p', q, and q', respectively;
|
303
|
-
# # if a bit is equal to one, then the corresonding node is referred to as an _active node_ (for example, if L = 1 then p is an
|
304
|
-
# # active node), and the subtree rooted at that node may contain a candidate point for p*. So the search is required to be
|
305
|
-
# # performed in the subtree rooted at all active nodes. More formally, at any instant of time the variables satisfy the folling
|
306
|
-
# # invariants:
|
307
|
-
# #
|
308
|
-
# # - If L = 1 the x(p) < x0.
|
309
|
-
# # - If L' = 1 then x0 <= x(p') <= x1.
|
310
|
-
# # - If R = 1 then x(q) > x1.
|
311
|
-
# # - If R' = 1 then x0 <= x(q') <= x1.
|
312
|
-
# # - If L' = 1 and R' = 1 then x(p') <= x(q').
|
313
|
-
# # - If P_Q is non-empty then p* = best or p* is in the subtree rooted at any one of the active nodes.
|
314
|
-
# #
|
315
|
-
# # There are more details in the paper
|
316
|
-
# update_highest = lambda do |node|
|
317
|
-
# t = val_at(node)
|
318
|
-
# if in_q.call(t) && t.y > best.y
|
319
|
-
# best = t
|
320
|
-
# end
|
321
|
-
# end
|
322
|
-
|
323
|
-
# ex_update_highest = lambda do |node|
|
324
|
-
# update_highest.call(node)
|
325
|
-
# update_highest.call(left(node)) unless leaf?(node)
|
326
|
-
# update_highest.call(right(node)) unless one_child?(node)
|
327
|
-
# end
|
328
|
-
|
329
|
-
# if val_at(root).x < x0
|
330
|
-
# p = root
|
331
|
-
# l = true
|
332
|
-
# l_prime = r = r_prime = false
|
333
|
-
# elsif val_at(root).x < x1
|
334
|
-
# p_prime = root
|
335
|
-
# l_prime = true
|
336
|
-
# l = r = r_prime = false
|
337
|
-
# else
|
338
|
-
# q = root
|
339
|
-
# r = true
|
340
|
-
# l = l_prime = r_prime = false
|
341
|
-
# end
|
342
|
-
|
343
|
-
# set_z = lambda do
|
344
|
-
# r = []
|
345
|
-
# r << p if l
|
346
|
-
# r << p_prime if l_prime
|
347
|
-
# r << q if r
|
348
|
-
# r << q_prime if r_primg
|
349
|
-
# r
|
350
|
-
# end
|
351
|
-
|
352
|
-
# check_left = lambda do
|
353
|
-
# if leaf?(p)
|
354
|
-
# l = false
|
355
|
-
# elsif one_child?(p)
|
356
|
-
# p_l_x = val_at(left(p))
|
357
|
-
# if x0 <= p_l_x && p_l_x <= x1
|
358
|
-
# update_highest.call(left(p))
|
359
|
-
# if l_prime && r_prime
|
360
|
-
# ex_update_highest.call(p_prime)
|
361
|
-
# elsif l_prime
|
362
|
-
# q_prime = p_prime
|
363
|
-
# r_prime = true
|
364
|
-
# end
|
365
|
-
# p_prime = left(p)
|
366
|
-
# l_prime = true
|
367
|
-
# l = false
|
368
|
-
# elsif p_l_x < x0
|
369
|
-
# p = left(p)
|
370
|
-
# else
|
371
|
-
# q = left(p)
|
372
|
-
# r = true
|
373
|
-
# l = false
|
374
|
-
# end
|
375
|
-
# else
|
376
|
-
# # p has two children
|
377
|
-
|
378
|
-
# end
|
379
|
-
|
380
|
-
# while l || l_prime || r || r_prime
|
381
|
-
# z_star = set_z.call.min_by(4) { level(_1) }
|
382
|
-
# if z_star.include? p_prime
|
383
|
-
# check_left_in(p_prime)
|
384
|
-
# elsif z_star.include? q_prime
|
385
|
-
# check_right_in(q_prime)
|
386
|
-
# elsif z_star.include? p
|
387
|
-
# check_left(p)
|
388
|
-
# else
|
389
|
-
# check_right(q)
|
390
|
-
# end
|
391
|
-
# end
|
392
|
-
# end
|
393
|
-
|
394
|
-
# Find the "highest" (max-y) point that is "northeast" of (x, y).
|
395
|
-
#
|
396
|
-
# That is, the point p* in Q = [x, infty) X [y, infty) with the largest y value, or (infty, -infty) if there is no point in that
|
397
|
-
# quadrant.
|
398
|
-
#
|
399
|
-
# Algorithm is from De et al. section 3.1
|
400
|
-
def highest_ne(x0, y0)
|
401
|
-
raise "Write me"
|
402
|
-
# From the paper:
|
403
|
-
#
|
404
|
-
# The algorithm uses two variables best and p, which satisfy the following invariant
|
405
|
-
#
|
406
|
-
# - If Q intersect P is nonempty then p* in {best} union T_p
|
407
|
-
# - If Q intersect P is empty then p* = best
|
408
|
-
#
|
409
|
-
# Here, P is the set of points in our data structure and T_p is the subtree rooted at p
|
410
|
-
best = Pair.new(INFINITY, -INFINITY)
|
411
|
-
p = root # root of the whole tree AND the pair stored there
|
412
|
-
|
413
|
-
in_q = lambda do |pair|
|
414
|
-
pair.x >= x0 && pair.y >= y0
|
415
|
-
end
|
416
|
-
|
417
|
-
# From the paper:
|
418
|
-
#
|
419
|
-
# takes as input a point t and does the following: if t \in Q and y(t) > y(best) then it assignes best = t
|
420
|
-
#
|
421
|
-
# Note that the paper identifies a node in the tree with its value. We need to grab the correct node.
|
422
|
-
update_highest = lambda do |node|
|
423
|
-
t = val_at(node)
|
424
|
-
if in_q.call(t) && t.y > best.y
|
425
|
-
best = t
|
426
|
-
end
|
427
|
-
end
|
428
|
-
|
429
|
-
# We could make this code more efficient. But since we only have O(log n) steps we won't actually gain much so let's keep it
|
430
|
-
# readable and close to the paper's pseudocode for now.
|
431
|
-
until leaf?(p)
|
432
|
-
p_val = val_at(p)
|
433
|
-
if in_q.call(p_val)
|
434
|
-
# p \in Q and nothing in its subtree can beat it because of the max-heap
|
435
|
-
update_highest.call(p)
|
436
|
-
return best
|
437
|
-
|
438
|
-
# p = left(p) <- from paper
|
439
|
-
elsif p_val.y < y0
|
440
|
-
# p is too low for Q, so the entire subtree is too low as well
|
441
|
-
return best
|
442
|
-
|
443
|
-
# p = left(p)
|
444
|
-
elsif one_child?(p)
|
445
|
-
# With just one child we need to check it
|
446
|
-
p = left(p)
|
447
|
-
elsif val_at(right(p)).x <= x0
|
448
|
-
# right(p) might be in Q, but nothing in the left subtree can be, by the PST property on x.
|
449
|
-
p = right(p)
|
450
|
-
elsif val_at(left(p)).x >= x0
|
451
|
-
# Both children are in Q, so try the higher of them. Note that nothing in either subtree will beat this one.
|
452
|
-
higher = left(p)
|
453
|
-
if val_at(right(p)).y > val_at(left(p)).y
|
454
|
-
higher = right(p)
|
455
|
-
end
|
456
|
-
p = higher
|
457
|
-
elsif val_at(right(p)).y < y0
|
458
|
-
# Nothing in the right subtree is in Q, but maybe we'll find something in the left
|
459
|
-
p = left(p)
|
460
|
-
else
|
461
|
-
# At this point we know that right(p) \in Q so we need to check it. Nothing in its subtree can beat it so we don't need to
|
462
|
-
# look there. But there might be something better in the left subtree.
|
463
|
-
update_highest.call(right(p))
|
464
|
-
p = left(p)
|
465
|
-
end
|
466
|
-
end
|
467
|
-
update_highest.call(p) # try the leaf
|
468
|
-
best
|
469
|
-
end
|
470
|
-
|
471
|
-
# O(n log^2 n)
|
472
|
-
private def construct_pst
|
473
|
-
# We follow the algorithm in [3]. Indexing is from 1 there and we follow that here. The algorithm is almost exactly the same as
|
474
|
-
# for the (max) PST.
|
475
|
-
h = Math.log2(@size).floor
|
476
|
-
a = @size - (2**h - 1) # the paper calls it A
|
477
|
-
sort_subarray(1, @size)
|
478
|
-
level = 0 # TODO: isn't level always equal to i in the loop?
|
479
|
-
|
480
|
-
(0...h).each do |i|
|
481
|
-
sense = level.even? ? :max : :min
|
482
|
-
pow_of_2 = 2**i
|
483
|
-
|
484
|
-
k = a / (2**(h - i))
|
485
|
-
k1 = 2**(h + 1 - i) - 1
|
486
|
-
k2 = (1 - k) * 2**(h - i) - 1 + a
|
487
|
-
k3 = 2**(h - i) - 1
|
488
|
-
(1..k).each do |j|
|
489
|
-
l = index_with_extremal_y_in(pow_of_2 + (j - 1) * k1, pow_of_2 + j * k1 - 1, sense:)
|
490
|
-
swap(l, pow_of_2 + j - 1)
|
491
|
-
end
|
492
|
-
|
493
|
-
if k < pow_of_2
|
494
|
-
l = index_with_extremal_y_in(pow_of_2 + k * k1, pow_of_2 + k * k1 + k2 - 1, sense:)
|
495
|
-
swap(l, pow_of_2 + k)
|
496
|
-
|
497
|
-
m = pow_of_2 + k * k1 + k2
|
498
|
-
(1..(pow_of_2 - k - 1)).each do |j|
|
499
|
-
l = index_with_extremal_y_in(m + (j - 1) * k3, m + j * k3 - 1, sense:)
|
500
|
-
swap(l, pow_of_2 + k + j)
|
501
|
-
end
|
502
|
-
end
|
503
|
-
sort_subarray(2 * pow_of_2, @size)
|
504
|
-
level += 1
|
505
|
-
end
|
506
|
-
end
|
507
|
-
|
508
|
-
########################################
|
509
|
-
# Indexing the data structure as though it were from 1, even though the underlying @data is indexed from zero.
|
510
|
-
|
511
|
-
# First element and root of the tree structure
|
512
|
-
private def root
|
513
|
-
1
|
514
|
-
end
|
515
|
-
|
516
|
-
private def val_at(idx)
|
517
|
-
@data[idx - 1]
|
518
|
-
end
|
519
|
-
|
520
|
-
# Indexing is from 1
|
521
|
-
private def parent(i)
|
522
|
-
i >> 1
|
523
|
-
end
|
524
|
-
|
525
|
-
private def left(i)
|
526
|
-
i << 1
|
527
|
-
end
|
528
|
-
|
529
|
-
private def right(i)
|
530
|
-
1 + (i << 1)
|
531
|
-
end
|
532
|
-
|
533
|
-
private def leaf?(i)
|
534
|
-
left(i) > @size
|
535
|
-
end
|
536
|
-
|
537
|
-
private def one_child?(i)
|
538
|
-
left(i) <= @size && right(i) > @size
|
539
|
-
end
|
540
|
-
|
541
|
-
private def swap(index1, index2)
|
542
|
-
return if index1 == index2
|
543
|
-
|
544
|
-
@data[index1 - 1], @data[index2 - 1] = @data[index2 - 1], @data[index1 - 1]
|
545
|
-
end
|
546
|
-
|
547
|
-
private def level(i)
|
548
|
-
count = 0
|
549
|
-
while i > root
|
550
|
-
i >>= 1
|
551
|
-
count += 1
|
552
|
-
end
|
553
|
-
count
|
554
|
-
end
|
555
|
-
|
556
|
-
# The index in @data[l..r] having the largest/smallest value for y
|
557
|
-
# The sense argument should be :min or :max
|
558
|
-
private def index_with_extremal_y_in(l, r, sense:)
|
559
|
-
return nil if r < l
|
560
|
-
|
561
|
-
case sense
|
562
|
-
when :min
|
563
|
-
(l..r).min_by { |idx| val_at(idx).y }
|
564
|
-
when :max
|
565
|
-
(l..r).max_by { |idx| val_at(idx).y }
|
566
|
-
else
|
567
|
-
raise "Bad comparison sense #{sense}"
|
568
|
-
end
|
569
|
-
end
|
570
|
-
|
571
|
-
# Sort the subarray @data[l..r]. This is much faster than a Ruby-layer heapsort because it is mostly happening in C.
|
572
|
-
private def sort_subarray(l, r)
|
573
|
-
# heapsort_subarray(l, r)
|
574
|
-
return if l == r # 1-array already sorted!
|
575
|
-
|
576
|
-
l -= 1
|
577
|
-
r -= 1
|
578
|
-
@data[l..r] = @data[l..r].sort_by(&:x)
|
579
|
-
end
|
580
|
-
|
581
|
-
########################################
|
582
|
-
# Debugging support
|
583
|
-
#
|
584
|
-
# These methods are not written for speed
|
585
|
-
|
586
|
-
# Check that our data satisfies the requirements of a Priority Search Tree:
|
587
|
-
# - max-heap in y
|
588
|
-
# - all the x values in the left subtree are less than all the x values in the right subtree
|
589
|
-
def verify_properties
|
590
|
-
# It's a min-max heap in y
|
591
|
-
(2..@size).each do |node|
|
592
|
-
level = Math.log2(node).floor
|
593
|
-
parent_level = level - 1
|
594
|
-
|
595
|
-
_, _, min_y, max_y = minmax_in_subtree(node)
|
596
|
-
parent_y = val_at(parent(node)).y
|
597
|
-
|
598
|
-
it_is_fine = if parent_level.even?
|
599
|
-
# max!
|
600
|
-
parent_y > max_y
|
601
|
-
else
|
602
|
-
parent_y < min_y
|
603
|
-
end
|
604
|
-
|
605
|
-
raise "Heap property violated at child #{node}" unless it_is_fine
|
606
|
-
end
|
607
|
-
|
608
|
-
# Left subtree has x values less than all of the right subtree
|
609
|
-
(1..@size).each do |node|
|
610
|
-
next if right(node) >= @size
|
611
|
-
|
612
|
-
left_max = max_x_in_subtree(left(node))
|
613
|
-
right_min = min_x_in_subtree(right(node))
|
614
|
-
|
615
|
-
raise "Left-right property of x-values violated at #{node}" unless left_max < right_min
|
616
|
-
end
|
617
|
-
|
618
|
-
nil
|
619
|
-
end
|
620
|
-
|
621
|
-
private def max_x_in_subtree(root)
|
622
|
-
minmax_in_subtree(root)[1]
|
623
|
-
end
|
624
|
-
|
625
|
-
private def min_x_in_subtree(root)
|
626
|
-
minmax_in_subtree(root)[0]
|
627
|
-
end
|
628
|
-
|
629
|
-
# Return min_x, max_x, min_y, max_y in subtree rooted at and including root
|
630
|
-
private def minmax_in_subtree(root)
|
631
|
-
@minmax_vals ||= []
|
632
|
-
@minmax_vals[root] ||= calc_minmax_at(root).freeze
|
633
|
-
end
|
634
|
-
|
635
|
-
# No memoization
|
636
|
-
private def calc_minmax_at(root)
|
637
|
-
return [INFINITY, -INFINITY, INFINITY, -INFINITY] if root > @size
|
638
|
-
|
639
|
-
pair = val_at(root)
|
640
|
-
|
641
|
-
return [pair.x, pair.x, pair.y, pair.y] if leaf?(root)
|
642
|
-
|
643
|
-
left = left(root)
|
644
|
-
left_min_max = minmax_in_subtree(left)
|
645
|
-
return left_min_max if one_child?(root)
|
646
|
-
|
647
|
-
right = right(root)
|
648
|
-
right_min_max = minmax_in_subtree(right)
|
649
|
-
|
650
|
-
[
|
651
|
-
[pair.x, left_min_max[0], right_min_max[0]].min,
|
652
|
-
[pair.x, left_min_max[1], right_min_max[1]].max,
|
653
|
-
[pair.y, left_min_max[2], right_min_max[2]].min,
|
654
|
-
[pair.y, left_min_max[3], right_min_max[3]].max
|
655
|
-
]
|
656
|
-
end
|
657
|
-
|
658
|
-
private def output_quasi_dot
|
659
|
-
(2..@size).to_a.reverse.map do |node|
|
660
|
-
"#{val_at(parent(node)).fmt} -- #{val_at(node).fmt}"
|
661
|
-
end.join("\n")
|
662
|
-
end
|
663
|
-
|
664
|
-
private def pair_to_s
|
665
|
-
end
|
666
|
-
|
667
|
-
########################################
|
668
|
-
# Dead code
|
669
|
-
|
670
|
-
end
|