GC: use simple balanced tree instead of AVL tree
This commit is contained in:
parent
52e8b597e4
commit
5e5ed192e5
7 changed files with 408 additions and 239 deletions
|
|
@ -7,245 +7,85 @@
|
|||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## not really an AVL tree anymore, but still balanced ...
|
||||
|
||||
## AVL balanced tree based on a C implementation by Julienne Walker
|
||||
|
||||
const
|
||||
HeightLimit = 128 # Tallest allowable tree
|
||||
|
||||
# Two way single rotation
|
||||
|
||||
template singleRot(root, dir: expr): stmt =
|
||||
block:
|
||||
var save = root.link[1-dir]
|
||||
root.link[1-dir] = save.link[dir]
|
||||
save.link[dir] = root
|
||||
root = save
|
||||
|
||||
# Two way double rotation
|
||||
|
||||
template doubleRot(root, dir: expr): stmt =
|
||||
block:
|
||||
var save = root.link[1-dir].link[dir]
|
||||
root.link[1-dir].link[dir] = save.link[1-dir]
|
||||
save.link[1-dir] = root.link[1-dir]
|
||||
root.link[1-dir] = save
|
||||
save = root.link[1-dir]
|
||||
root.link[1-dir] = save.link[dir]
|
||||
save.link[dir] = root
|
||||
root = save
|
||||
|
||||
# Adjust balance before double rotation
|
||||
|
||||
template adjustBalance(root, dir, bal: expr): stmt =
|
||||
block:
|
||||
var n = root.link[dir]
|
||||
var nn = n.link[1-dir]
|
||||
if nn.balance == 0:
|
||||
root.balance = 0
|
||||
n.balance = 0
|
||||
elif nn.balance == bal:
|
||||
root.balance = -bal
|
||||
n.balance = 0
|
||||
else:
|
||||
# nn->balance == -bal
|
||||
root.balance = 0
|
||||
n.balance = bal
|
||||
nn.balance = 0
|
||||
|
||||
# Rebalance after insertion
|
||||
|
||||
template insertBalance(root, dir: expr): stmt =
|
||||
block:
|
||||
var n = root.link[dir]
|
||||
var bal = if dir == 0: -1 else: +1
|
||||
if n.balance == bal:
|
||||
root.balance = 0
|
||||
n.balance = 0
|
||||
singleRot(root, 1-dir)
|
||||
else:
|
||||
# n->balance == -bal
|
||||
adjustBalance(root, dir, bal)
|
||||
doubleRot(root, 1-dir)
|
||||
|
||||
# Rebalance after deletion
|
||||
|
||||
template removeBalance(root, dir, done: expr): stmt =
|
||||
block:
|
||||
var n = root.link[1-dir]
|
||||
var bal = if dir == 0: -1 else: + 1
|
||||
if n.balance == - bal:
|
||||
root.balance = 0
|
||||
n.balance = 0
|
||||
singleRot(root, dir)
|
||||
elif n.balance == bal:
|
||||
adjustBalance(root, 1-dir, - bal)
|
||||
doubleRot(root, dir)
|
||||
else:
|
||||
# n->balance == 0
|
||||
root.balance = -bal
|
||||
n.balance = bal
|
||||
singleRot(root, dir)
|
||||
done = true
|
||||
|
||||
proc find(root: PAvlNode, key: int): PAvlNode =
|
||||
var it = root
|
||||
while it != nil:
|
||||
if it.key == key: return it
|
||||
it = it.link[ord(it.key <% key)]
|
||||
|
||||
proc inRange(root: PAvlNode, key: int): PAvlNode =
|
||||
var it = root
|
||||
while it != nil:
|
||||
if it.key <=% key and key <=% it.upperBound: return it
|
||||
it = it.link[ord(it.key <% key)]
|
||||
|
||||
proc contains(root: PAvlNode, key: int): bool {.inline.} =
|
||||
result = find(root, key) != nil
|
||||
|
||||
proc maxheight(n: PAvlNode): int =
|
||||
if n != nil:
|
||||
result = max(maxheight(n.link[0]), maxheight(n.link[1])) + 1
|
||||
|
||||
proc minheight(n: PAvlNode): int =
|
||||
if n != nil:
|
||||
result = min(minheight(n.link[0]), minheight(n.link[1])) + 1
|
||||
template IsBottom(n: PAvlNode): bool = n == bottom
|
||||
|
||||
proc lowGauge(n: PAvlNode): int =
|
||||
var it = n
|
||||
while it != nil:
|
||||
while not IsBottom(it):
|
||||
result = it.key
|
||||
it = it.link[0]
|
||||
|
||||
proc highGauge(n: PAvlNode): int =
|
||||
result = -1
|
||||
var it = n
|
||||
while it != nil:
|
||||
while not IsBottom(it):
|
||||
result = it.upperBound
|
||||
it = it.link[1]
|
||||
|
||||
proc add(a: var TMemRegion, key, upperBound: int) =
|
||||
# Empty tree case
|
||||
if a.root == nil:
|
||||
a.root = allocAvlNode(a, key, upperBound)
|
||||
proc find(root: PAvlNode, key: int): PAvlNode =
|
||||
var it = root
|
||||
while not IsBottom(it):
|
||||
if it.key == key: return it
|
||||
it = it.link[ord(it.key <% key)]
|
||||
|
||||
proc inRange(root: PAvlNode, key: int): PAvlNode =
|
||||
var it = root
|
||||
while not IsBottom(it):
|
||||
if it.key <=% key and key <% it.upperBound: return it
|
||||
it = it.link[ord(it.key <% key)]
|
||||
|
||||
proc skew(t: var PAvlNode) =
|
||||
if t.link[0].level == t.level:
|
||||
var temp = t
|
||||
t = t.link[0]
|
||||
temp.link[0] = t.link[1]
|
||||
t.link[1] = temp
|
||||
|
||||
proc split(t: var PAvlNode) =
|
||||
if t.link[1].link[1].level == t.level:
|
||||
var temp = t
|
||||
t = t.link[1]
|
||||
temp.link[1] = t.link[0]
|
||||
t.link[0] = temp
|
||||
inc t.level
|
||||
|
||||
proc add(a: var TMemRegion, t: var PAvlNode, key, upperBound: int) =
|
||||
if t == bottom:
|
||||
t = allocAvlNode(a, key, upperBound)
|
||||
else:
|
||||
var head: TAvlNode # Temporary tree root
|
||||
var s, t, p, q: PAvlNode
|
||||
# Iterator and save pointer
|
||||
var dir: int
|
||||
# Set up false root to ease maintenance:
|
||||
t = addr(head)
|
||||
t.link[1] = a.root
|
||||
# Search down the tree, saving rebalance points
|
||||
s = t.link[1]
|
||||
p = s
|
||||
while true:
|
||||
dir = ord(p.key <% key)
|
||||
q = p.link[dir]
|
||||
if q == nil: break
|
||||
if q.balance != 0:
|
||||
t = p
|
||||
s = q
|
||||
p = q
|
||||
q = allocAvlNode(a, key, upperBound)
|
||||
p.link[dir] = q
|
||||
# Update balance factors
|
||||
p = s
|
||||
while p != q:
|
||||
dir = ord(p.key <% key)
|
||||
if dir == 0: dec p.balance
|
||||
else: inc p.balance
|
||||
p = p.link[dir]
|
||||
q = s
|
||||
# Save rebalance point for parent fix
|
||||
# Rebalance if necessary
|
||||
if abs(s.balance) > 1:
|
||||
dir = ord(s.key <% key)
|
||||
insertBalance(s, dir)
|
||||
# Fix parent
|
||||
if q == head.link[1]: a.root = s
|
||||
else: t.link[ord(q == t.link[1])] = s
|
||||
if key <% t.key:
|
||||
add(a, t.link[0], key, upperBound)
|
||||
elif key >% t.key:
|
||||
add(a, t.link[1], key, upperBound)
|
||||
else:
|
||||
sysAssert false, "key already exists"
|
||||
skew(t)
|
||||
split(t)
|
||||
|
||||
proc del(a: var TMemRegion, key: int) =
|
||||
if a.root == nil: return
|
||||
var
|
||||
upd: array[0..HeightLimit-1, int]
|
||||
up: array[0..HeightLimit-1, PAvlNode]
|
||||
var top = 0
|
||||
var it = a.root
|
||||
# Search down tree and save path
|
||||
while true:
|
||||
if it == nil: return
|
||||
elif it.key == key: break
|
||||
# Push direction and node onto stack
|
||||
upd[top] = ord(it.key <% key)
|
||||
up[top] = it
|
||||
it = it.link[upd[top]]
|
||||
inc top
|
||||
# Remove the node
|
||||
if it.link[0] == nil or it.link[1] == nil:
|
||||
# Which child is not null?
|
||||
var dir = ord(it.link[0] == nil)
|
||||
# Fix parent
|
||||
if top != 0: up[top - 1].link[upd[top - 1]] = it.link[dir]
|
||||
else: a.root = it.link[dir]
|
||||
deallocAvlNode(a, it)
|
||||
proc del(a: var TMemRegion, t: var PAvlNode, x: int) =
|
||||
if t == bottom: return
|
||||
a.last = t
|
||||
if x <% t.key:
|
||||
del(a, t.link[0], x)
|
||||
else:
|
||||
# Find the inorder successor
|
||||
var heir = it.link[1]
|
||||
# Save this path too
|
||||
upd[top] = 1
|
||||
up[top] = it
|
||||
inc top
|
||||
while heir.link[0] != nil:
|
||||
upd[top] = 0
|
||||
up[top] = heir
|
||||
inc top
|
||||
heir = heir.link[0]
|
||||
swap(it.key, heir.key)
|
||||
swap(it.upperBound, heir.upperBound)
|
||||
|
||||
# Unlink successor and fix parent
|
||||
up[top - 1].link[ord(up[top - 1] == it)] = heir.link[1]
|
||||
deallocAvlNode(a, heir)
|
||||
# Walk back up the search path
|
||||
dec top
|
||||
var done = false
|
||||
while top >= 0 and not done:
|
||||
# Update balance factors
|
||||
if upd[top] != 0: dec up[top].balance
|
||||
else: inc up[top].balance
|
||||
# Terminate or rebalance as necessary
|
||||
if abs(up[top].balance) == 1:
|
||||
break
|
||||
elif abs(up[top].balance) > 1:
|
||||
removeBalance(up[top], upd[top], done)
|
||||
# Fix parent
|
||||
if top != 0: up[top-1].link[upd[top-1]] = up[top]
|
||||
else: a.root = up[0]
|
||||
dec top
|
||||
|
||||
when isMainModule:
|
||||
import math
|
||||
var
|
||||
r: PAvlNode
|
||||
s: seq[int]
|
||||
const N = 1000_000
|
||||
newSeq s, N
|
||||
|
||||
for i in 0..N-1:
|
||||
var key = i #random(10_000)
|
||||
s[i] = key
|
||||
r.add(key, 12_000_000)
|
||||
for i in 0..N-1:
|
||||
var key = s[i]
|
||||
doAssert inRange(r, key+1000) != nil
|
||||
doAssert key in r
|
||||
echo "Min-Height: ", minheight(r), " max-height: ", maxheight(r)
|
||||
for i in 0..N-1:
|
||||
var key = s[i]
|
||||
del r, key
|
||||
doAssert key notin r
|
||||
|
||||
doAssert r == nil
|
||||
a.deleted = t
|
||||
del(a, t.link[1], x)
|
||||
if t == a.last and a.deleted != bottom and x == a.deleted.key:
|
||||
a.deleted.key = t.key
|
||||
a.deleted.upperBound = t.upperBound
|
||||
a.deleted = bottom
|
||||
t = t.link[1]
|
||||
deallocAvlNode(a, a.last)
|
||||
elif t.link[0].level < t.level-1 or
|
||||
t.link[1].level < t.level-1:
|
||||
dec t.level
|
||||
if t.link[1].level > t.level:
|
||||
t.link[1].level = t.level
|
||||
skew(t)
|
||||
skew(t.link[1])
|
||||
skew(t.link[1].link[1])
|
||||
split(t)
|
||||
split(t.link[1])
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue