In the following, \text{expose}(v) extracts the left child l, key k, and right child r of node v into a tuple (l,k,r) . \text{Node}(l,k,r) creates a node with left child l, key k and right child r. "s_1 || s_2" means that two statements s_1 and s_2 can run in parallel.
Split To split a tree into two trees, those smaller than key
x, and those larger than key
x, we first draw a path from the root by inserting
x into the tree. After this insertion, all values less than
x will be found on the left of the path, and all values greater than
x will be found on the right. By applying
Join, all the subtrees on the left side are merged bottom-up using keys on the path as intermediate nodes from bottom to top to form the left tree, and the right part is asymmetric. For some applications,
Split also returns a boolean value denoting if
x appears in the tree. The cost of
Split is O(\log n), order of the height of the tree. The split algorithm is as follows:
function split(T, k)
if (T = nil)
return (nil, false, nil)
else (L, m, R) := expose(T)
if k m (L', b, R') := split(R, k)
return (join(L, m, L'), b, R'))
else return (L, true, R)
Join2 This function is defined similarly as
join but without the middle key. It first splits out the last key k of the left tree, and then join the rest part of the left tree with the right tree with k. The algorithm is as follows:
function splitLast(T) (L, k, R) := expose(T)
if R = nil
return (L, k)
else (T', k') := splitLast(R)
return (join(L, k, T'), k')
function join2(L, R)
if L = nil
return R
else (L', k) := splitLast(L)
return join(L', k, R) The cost is O(\log n) for a tree of size n.
Insert and delete The insertion and deletion algorithms, when making use of
join can be independent of balancing schemes. For an insertion, the algorithm compares the key to be inserted with the key in the root, inserts it to the left/right subtree if the key is smaller/greater than the key in the root, and joins the two subtrees back with the root. A deletion compares the key to be deleted with the key in the root. If they are equal, return join2 on the two subtrees. Otherwise, delete the key from the corresponding subtree, and join the two subtrees back with the root. The algorithms are as follows:
function insert(T, k)
if T = nil
return Node(nil, k, nil)
else (L, k', R) := expose(T)
if k k'
return join(L, k', insert(R, k))
else return T
function delete(T, k)
if T = nil
return nil
else (L, k', R) := expose(T)
if k k'
return join(L, k', delete(R, k))
else return join2(L, R) Both insertion and deletion requires O(\log n) time if |T|=n.
Set–set functions Several set operations have been defined on weight-balanced trees:
union,
intersection and
set difference. The union of two weight-balanced trees and representing sets and , is a tree that represents . The following recursive function computes this union:
function union(t1, t2)
if t1 = nil
return t2
else if t2 = nil
return t1
else (l1, k1, r1) := expose(t1) (t, b, t>) := split(t2, k1) l' := union(l1, t) || r' := union(r1, t>)
return join(l', k1, r') Similarly, the algorithms of intersection and set-difference are as follows:
function intersection(t1, t2)
if t1 = nil
or t2 = nil
return nil
else (l1, k1, r1) := expose(t1) (t, b, t>) = split(t2, k1) l' := intersection(l1, t) || r' := intersection(r1, t>)
if b
return join(l', k1, r')
else return join2(l', r')
function difference(t1, t2)
if t1 = nil
return nil
else if t2 = nil
return t1
else (l1, k1, r1) := expose(t1) (t, b, t>) := split(t2, k1) l' = difference(l1, t) || r' = difference(r1, t>)
if b
return join2(l', r')
else return join(l', k1, r') The complexity of each of union, intersection and difference is O\left(m \log \left(\tfrac{n}{m}+1\right)\right) for two weight-balanced trees of sizes m and n(\ge m). This complexity is optimal in terms of the number of comparisons. More importantly, since the recursive calls to union, intersection or difference are independent of each other, they can be executed
in parallel with a
parallel depth O(\log m\log n). When m=1, the join-based implementation applies the same computation as in a single-element insertion or deletion if the root of the larger tree is used to split the smaller tree.
Build The algorithm for building a tree can make use of the union algorithm, and use the divide-and-conquer scheme:
function build(A[], n)
if n = 0
return nil
else if n = 1
return Node(nil, A[0], nil)
else l' := build(A, n/2) || r' := (A+n/2, n-n/2)
return union(L, R) This algorithm costs O(n\log n) work and has O(\log^3 n) depth. A more-efficient algorithm makes use of a parallel sorting algorithm.
function buildSorted(A[], n)
if n = 0
return nil
else if n = 1
return Node(nil, A[0], nil)
else l' := build(A, n/2) || r' := (A+n/2+1, n-n/2-1)
return join(l', A[n/2], r')
function build(A[], n) A' := sort(A, n)
return buildSorted(A, n) This algorithm costs O(n\log n) work and has O(\log n) depth assuming the sorting algorithm has O(n\log n) work and O(\log n) depth.
Filter This function selects all entries in a tree satisfying a predicate p, and return a tree containing all selected entries. It recursively filters the two subtrees, and join them with the root if the root satisfies p, otherwise
join2 the two subtrees.
function filter(T, p)
if T = nil
return nil
else (l, k, r) := expose(T) l' := filter(l, p) || r' := filter(r, p)
if p(k)
return join(l', k, r')
else return join2(l', R) This algorithm costs work O(n) and depth O(\log^2 n) on a tree of size n, assuming p has constant cost. ==Used in libraries==