Let us define a function split(T,v)
that takes in a tree, T
and a value to split at, v
. Suppose that each node of the tree stores its left child and right child in addition to the value at that node. Use the following algorithm:
First we check to see if the input tree is simply a leaf or not.
If T
is not a leaf, compare the value of its root node, v'
with v
.
If v' < v
then recursively call split on the left subtree. Store the values of the recursive call as L'
(returned left tree), R'
(returned right tree), and r
(option type indicated if the value v
was found or not). Construct the new right tree, newR = Node(R',v',R)
, and return (L',r,newR)
.
Else if v' > v
then recursively call split on the right subtree. Store the values of the recursive call as L'
(returned left tree), R'
(returned right tree), and r
(option type indicated if the value v
was found or not). Construct the new left tree, newL = Node(L',v',L)
, and return (newL,r,R')
.
Else if v' = v
, return L, SOME(v), R
.
If T
is a leaf, we must have reached the root of the tree without finding the input value v to split at. Return that you couldn't find the leaf by passing back NONE
.
Why is this logarithmic? Well, you only ever traverse one root-to-leaf path of the tree, at most. We can easily reconstruct nodes in constant time since we're just re-assigning O(logn) references (in an imperative language) or reassigning O(logn) values that take a constant time to generate (in a functional language).
Here's the corresponding code for the algorithm. It's written in SML, but I'd be willing to clarify what anything means in the comments.
fun split(T,v) =
case T of
Leaf => (Leaf, NONE, Leaf)
| Node(L,v,R) =>
case compare(v, v') of
LESS =>
let
val (L',r,R') = split(L,k)
in
(L',r,Node(R',r,R))
end
| GREATER =>
let
val (L',r,R') = split(R,k)
in
(Node(L',v',L),r,R')
end
| EQUAL => (L, SOME(v), R)
See this document for more details. It provides a more thorough explanation of the above.