Skip to content

Add utility functions to traverse trees to find minimum, maximum node and the keys adjacent to a node #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Feb 26, 2025
86 changes: 86 additions & 0 deletions src/binarytree.jl
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,89 @@ function _printkeyvalue(io::IO, node::BinaryNode)
show(ioctx, val)
end
end

# -----------
# UTILITIES
# -----------

"""
BinaryTrees.minnode(tree)

Find the `node` with the smallest `key` in the `tree`.

BinaryTrees.minnode(node)

Find the `node` with the smallest `key` in the subtree rooted at `node`.
If `nothing` is provided, `nothing` is returned.
"""
minnode(tree::BinaryTree) = minnode(root(tree))

function minnode(node::BinaryNode)
leftnode = left(node)
isnothing(leftnode) ? node : minnode(leftnode)
end

minnode(node::Nothing) = nothing

"""
BinaryTrees.maxnode(tree)

Find the `node` with the maximum `key` in the `tree`.

BinaryTrees.maxnode(node)

Find the `node` with the maximum `key` in the subtree rooted at `node`.
If `nothing` is provided, `nothing` is returned.
"""
maxnode(tree::BinaryTree) = maxnode(root(tree))

function maxnode(node::BinaryNode)
rightnode = right(node)
isnothing(rightnode) ? node : maxnode(rightnode)
end

maxnode(node::Nothing) = nothing

"""
BinaryTrees.prevnext(tree, k)

Returns a `tuple` of each `node` immediately before
and after the `node` with `key`, `k` within `tree`.

If an adjacent `node` does not exist, `nothing` is returned in its place.
If `k` is `nothing`, returns `(nothing, nothing)`.
"""
function prevnext(tree::BinaryTree, k)
prev, next = nothing, nothing
current = root(tree)
# traverse from the root to the target node, updating candidates
while !isnothing(current) && key(current) != k
if k < key(current)
# current is a potential next (successor)
next = current
current = left(current)
else # k > key(current)
# current is a potential previous (predecessor)
prev = current
current = right(current)
end
end

# if the node wasn't found, return the best candidate values
if isnothing(current)
return (prev, next)
end

# if there is a left subtree, the true previous (predecessor) is the maximum in that subtree
if !isnothing(left(current))
prev = maxnode(left(current))
end
# similarly, if there is a right subtree, the true next (successor) is the minimum in that subtree
if !isnothing(right(current))
next = minnode(right(current))
end

(prev, next)
end

prevnext(tree::BinaryTree, k::Nothing) = (nothing, nothing)
38 changes: 37 additions & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ const BT = BinaryTrees
BT.insert!(tree, 2, 20)
BT.insert!(tree, 1, 10)
BT.insert!(tree, 3, 30)
# deleting a key that does not exist
# deleting a key that does not exist
# does not change the tree
BT.delete!(tree, 4)
@test tree === tree
Expand Down Expand Up @@ -219,11 +219,32 @@ const BT = BinaryTrees
@test BT.value(BT.search(tree, (0, 0, 1))) == 1
@test BT.value(BT.search(tree, (1, 0, 0))) == 3

# traversal algorithms
tree = AVLTree{Int,Float64}()
BT.insert!(tree, 0, 5)
BT.insert!(tree, 1, 6)
BT.insert!(tree, 2, 8)
BT.insert!(tree, 3, 10)
BT.insert!(tree, 4, 20)
BT.insert!(tree, 5, 30)
BT.insert!(tree, 6, 40)
@test BT.key(BT.minnode(tree)) == 0
@test BT.key(BT.maxnode(tree)) == 6
@test BT.prevnext(tree, 0)[1] == nothing
@test BT.key.(BT.prevnext(tree, 2)) == (1, 3)
@test BT.key.(BT.prevnext(tree, 5)) == (4, 6)
@test BT.prevnext(tree, nothing) == (nothing, nothing)

# type stability
tree = AVLTree{Int,Int}()
@inferred BT.insert!(tree, 2, 20)
@inferred BT.insert!(tree, 1, 10)
@inferred BT.insert!(tree, 3, 30)
@inferred Nothing BT.minnode(tree)
@inferred Nothing BT.maxnode(tree)
@inferred Nothing BT.prevnext(tree, 2)[1]
@inferred Nothing BT.prevnext(tree, 2)[2]
@inferred BT.prevnext(tree, nothing)
@inferred Nothing BT.search(tree, 2)
@inferred Nothing BT.search(tree, 1)
@inferred Nothing BT.search(tree, 3)
Expand All @@ -234,6 +255,11 @@ const BT = BinaryTrees
@inferred BT.insert!(tree, 2)
@inferred BT.insert!(tree, 1)
@inferred BT.insert!(tree, 3)
@inferred Nothing BT.minnode(tree)
@inferred Nothing BT.maxnode(tree)
@inferred Nothing BT.prevnext(tree, 2)[1]
@inferred Nothing BT.prevnext(tree, 2)[2]
@inferred BT.prevnext(tree, nothing)
@inferred Nothing BT.search(tree, 2)
@inferred Nothing BT.search(tree, 1)
@inferred Nothing BT.search(tree, 3)
Expand All @@ -244,6 +270,11 @@ const BT = BinaryTrees
@inferred BT.insert!(tree, "key2", 2)
@inferred BT.insert!(tree, "key1", 1)
@inferred BT.insert!(tree, "key3", 3)
@inferred Nothing BT.minnode(tree)
@inferred Nothing BT.maxnode(tree)
@inferred Nothing BT.prevnext(tree, "key2")[1]
@inferred Nothing BT.prevnext(tree, "key2")[2]
@inferred BT.prevnext(tree, nothing)
@inferred Nothing BT.search(tree, "key2")
@inferred Nothing BT.search(tree, "key1")
@inferred Nothing BT.search(tree, "key3")
Expand All @@ -254,6 +285,11 @@ const BT = BinaryTrees
@inferred BT.insert!(tree, (0, 1, 0), 2)
@inferred BT.insert!(tree, (0, 0, 1), 1)
@inferred BT.insert!(tree, (1, 0, 0), 3)
@inferred Nothing BT.minnode(tree)
@inferred Nothing BT.maxnode(tree)
@inferred Nothing BT.prevnext(tree, (0, 1, 0))[1]
@inferred Nothing BT.prevnext(tree, (0, 1, 0))[2]
@inferred BT.prevnext(tree, nothing)
@inferred Nothing BT.search(tree, (0, 1, 0))
@inferred Nothing BT.search(tree, (0, 0, 1))
@inferred Nothing BT.search(tree, (1, 0, 0))
Expand Down