198 lines
5.6 KiB
Nim
198 lines
5.6 KiB
Nim
#[ AVL tree adapted from Julienne Walker's presentation at
|
|
http://eternallyconfuzzled.com/tuts/datastructures/jsw_tut_avl.aspx.
|
|
|
|
Uses bounded recursive versions for insertion and deletion.
|
|
]#
|
|
|
|
type
|
|
|
|
# Objects strored in the tree must be comparable.
|
|
Comparable = concept x, y
|
|
(x == y) is bool
|
|
(x < y) is bool
|
|
|
|
# Direction used to select a child.
|
|
Direction = enum Left, Right
|
|
|
|
# Description of the tree node.
|
|
Node[T: Comparable] = ref object
|
|
data: T # Payload.
|
|
balance: range[-2..2] # Balance factor (bounded).
|
|
links: array[Direction, Node[T]] # Children.
|
|
|
|
# Description of a tree.
|
|
AvlTree[T: Comparable] = object
|
|
root: Node[T]
|
|
|
|
|
|
#---------------------------------------------------------------------------------------------------
|
|
|
|
func opp(dir: Direction): Direction {.inline.} =
|
|
## Return the opposite of a direction.
|
|
Direction(1 - ord(dir))
|
|
|
|
#---------------------------------------------------------------------------------------------------
|
|
|
|
func single(root: Node; dir: Direction): Node =
|
|
## Single rotation.
|
|
|
|
result = root.links[opp(dir)]
|
|
root.links[opp(dir)] = result.links[dir]
|
|
result.links[dir] = root
|
|
|
|
#---------------------------------------------------------------------------------------------------
|
|
|
|
func double(root: Node; dir: Direction): Node =
|
|
## Double rotation.
|
|
|
|
let save = root.links[opp(dir)].links[dir]
|
|
|
|
root.links[opp(dir)].links[dir] = save.links[opp(dir)]
|
|
save.links[opp(dir)] = root.links[opp(dir)]
|
|
root.links[opp(dir)] = save
|
|
|
|
result = root.links[opp(dir)]
|
|
root.links[opp(dir)] = result.links[dir]
|
|
result.links[dir] = root
|
|
|
|
#---------------------------------------------------------------------------------------------------
|
|
|
|
func adjustBalance(root: Node; dir: Direction; balance: int) =
|
|
## Adjust balance factors after double rotation.
|
|
|
|
let node1 = root.links[dir]
|
|
let node2 = node1.links[opp(dir)]
|
|
|
|
if node2.balance == 0:
|
|
root.balance = 0
|
|
node1.balance = 0
|
|
|
|
elif node2.balance == balance:
|
|
root.balance = -balance
|
|
node1.balance = 0
|
|
|
|
else:
|
|
root.balance = 0
|
|
node1.balance = balance
|
|
|
|
node2.balance = 0
|
|
|
|
#---------------------------------------------------------------------------------------------------
|
|
|
|
func insertBalance(root: Node; dir: Direction): Node =
|
|
## Rebalancing after an insertion.
|
|
|
|
let node = root.links[dir]
|
|
let balance = 2 * ord(dir) - 1
|
|
|
|
if node.balance == balance:
|
|
root.balance = 0
|
|
node.balance = 0
|
|
result = root.single(opp(dir))
|
|
|
|
else:
|
|
root.adjustBalance(dir, balance)
|
|
result = root.double(opp(dir))
|
|
|
|
#---------------------------------------------------------------------------------------------------
|
|
|
|
func insertR(root: Node; data: root.T): tuple[node: Node, done: bool] =
|
|
## Insert data (recursive way).
|
|
|
|
if root.isNil:
|
|
return (Node(data: data), false)
|
|
|
|
let dir = if root.data < data: Right else: Left
|
|
var done: bool
|
|
(root.links[dir], done) = root.links[dir].insertR(data)
|
|
if done:
|
|
return (root, true)
|
|
|
|
inc root.balance, 2 * ord(dir) - 1
|
|
result = case root.balance
|
|
of 0: (root, true)
|
|
of -1, 1: (root, false)
|
|
else: (root.insertBalance(dir), true)
|
|
|
|
#---------------------------------------------------------------------------------------------------
|
|
|
|
func removeBalance(root: Node; dir: Direction): tuple[node: Node, done: bool] =
|
|
## Rebalancing after a deletion.
|
|
|
|
let node = root.links[opp(dir)]
|
|
let balance = 2 * ord(dir) - 1
|
|
if node.balance == -balance:
|
|
root.balance = 0
|
|
node.balance = 0
|
|
result = (root.single(dir), false)
|
|
elif node.balance == balance:
|
|
root.adjustBalance(opp(dir), -balance)
|
|
result = (root.double(dir), false)
|
|
else:
|
|
root.balance = -balance
|
|
node.balance = balance
|
|
result = (root.single(dir), true)
|
|
|
|
#---------------------------------------------------------------------------------------------------
|
|
|
|
func removeR(root: Node; data: root.T): tuple[node: Node, done: bool] =
|
|
## Remove data (recursive way).
|
|
|
|
if root.isNil:
|
|
return (nil, false)
|
|
|
|
var data = data
|
|
if root.data == data:
|
|
if root.links[Left].isNil:
|
|
return (root.links[Right], false)
|
|
if root.links[Right].isNil:
|
|
return (root.links[Left], false)
|
|
var heir = root.links[Left]
|
|
while not heir.links[Right].isNil:
|
|
heir = heir.links[Right]
|
|
root.data = heir.data
|
|
data = heir.data
|
|
|
|
let dir = if root.data < data: Right else: Left
|
|
var done: bool
|
|
(root.links[dir], done) = root.links[dir].removeR(data)
|
|
if done:
|
|
return (root, true)
|
|
dec root.balance, 2 * ord(dir) - 1
|
|
result = case root.balance
|
|
of -1, 1: (root, true)
|
|
of 0: (root, false)
|
|
else: root.removeBalance(dir)
|
|
|
|
#---------------------------------------------------------------------------------------------------
|
|
|
|
func insert(tree: var AvlTree; data: tree.T) =
|
|
## Insert data in an AVL tree.
|
|
tree.root = tree.root.insertR(data).node
|
|
|
|
#---------------------------------------------------------------------------------------------------
|
|
|
|
func remove(tree: var AvlTree; data: tree.T) =
|
|
## Remove data from an AVL tree.
|
|
tree.root = tree.root.removeR(data).node
|
|
|
|
#———————————————————————————————————————————————————————————————————————————————————————————————————
|
|
|
|
import json
|
|
|
|
var tree: AvlTree[int]
|
|
echo pretty(%tree)
|
|
|
|
echo "Insert test:"
|
|
tree.insert(3)
|
|
tree.insert(1)
|
|
tree.insert(4)
|
|
tree.insert(1)
|
|
tree.insert(5)
|
|
echo pretty(%tree)
|
|
|
|
echo ""
|
|
echo "Remove test:"
|
|
tree.remove(3)
|
|
tree.remove(1)
|
|
echo pretty(%tree)
|