Nov 6

Join

To implement intersection and difference, we need another utility function join. join takes as input two treaps \(v_1\) and \(v_2\) where every element in \(v_1\) is smaller than every element in \(v_2\). It returns a new treap with the union of the elements from \(v_1\) and \(v_2\). Note this is simpler than the union operation since we know that the elements in \(v_1\) and \(v_2\) are separated.

join(v1, v2):
    if v1 == nil:
        return v2
    if v2 == nil:
        return v1

    if v1.p < v2.p:
        # Make a clone of v1 the new root
        w = new node
        w.x = v1.x
        w.p = v1.p

        # We now need to merge v2 with the children of w.  But everything
        # in v2 is larger than v1, so it only needs to be merged with the
        # right child of w.
        w.left = v1.left
        w.right = join(v1.right, v2)

        return w

    else:
        # Make a clone of v2 as the new root
        w = new node
        w.x = v2.x
        w.p = v2.p

        # Merge v1 with the left, since everything in v1 is smaller than v2.x
        w.left = join(v1, v2.left)
        w.right = v2.right

        return w

Join runs in time \(O(height)\) so expected time \(O(\log n)\).

Correctness

Lemma Let \(v_1\) and \(v_2\) be roots of treaps, where in addition for each node \(s\) in the treap rooted at \(v_1\) and each node \(t\) in the treap rooted at \(v_2\), we have that \(s.x < t.x\). Let \(w = join(v_1, v_2)\). Then \(w\) is the root of a treap consisting of the union of elements from \(v_1\) and \(v_2\).

Proof The proof is by induction on the sum of the size of \(v_1\) and \(v_2\).

Intersection

Intersection then is implemented as follows. The idea is to basically copy the union code, but make a minor change in that we don't always add an element, we only add it if it appears in both v1 and v2. If we don't add the element, we must join the children together.

intersection(v1, v2):
    if v1 == nil or v2 == nil:
        return nil

    if v1.p < v2.p:
        # v1 might become the new root, so split v2 around v1.x
        (r1, r2, a) = split(v2, v1.x)

        # Now, intersect the stuff smaller than v1.x which is v1.left and r1
        left = intersect(v1.left, r1)

        # Now intersect the stuff larger than v1.x which is v1.right and r2
        right = intersect(v1.right, r2)

        if a == nil:
            # v1.x is not in v2, so unlike union don't add v1.  Instead, we have to join left and
            # right to form a new treep without v1.
            return join(left, right)

        else:
            # Similar to union, create a new clone of v1
            w = new node
            w.x = v1.x
            w.p = v1.p
            w.left = left
            w.right = right
            return w

    else:
        # v2 might become the new root, so split v1 around v2.x
        (r1, r2, a) = split(v1, v2.x)

        # Compute the left and right subtrees
        left = intersect(r1, v2.left)
        right = intersect(r2, v2.right)

        if a == nil:
            # v2.x is not in v1, so don't add v2.  Instead, join
            return join(left, right)

        else:
            # Like union, create a new clone of v2 but use the a.x value so
            # the intersection takes elements from v1
            w = new node
            w.x = a.x
            w.p = v2.p
            w.left = left
            w.right = right
            return w

Correctness

Lemma Let \(v_1\) and \(v_2\) be roots of treaps and let \(w = intersection(v_1, v_2)\). Then \(w\) is the root of a treap consisting of all elements which appear in both \(v_1\) and \(v_2\). The intersection is left-biased, so takes elements from \(v_1\).

Proof The proof is by induction on the sum of the size of \(v_1\) and \(v_2\).

Difference

Difference is very similar to intersection!

Remove

To remove an element from a treap, we can just use difference to subtract a singleton tree consisting just of the element we want to remove.

remove(v, x):
    # v is the root of a treap and x is an element
    # the return value is a new treap with x removed from v if it exists.
    # v is not changed

    w = new node
    w.x = x
    w.p = random number
    return difference(v, w)

Implementation

persist-treap.cpp contains my implementation. For the most part is is a direct translation of the pseudocode. The recursive version of split does use a C++ tuple and tie to return tuples. Also, I describe a space-efficient version of split as well.

Exercise

Write pseudocode for difference and then fill in the implementation of difference inside persist-treap.cpp. As mentioned above, it is very similar to intersection so you can base it off of the C++ code for intersection. Finally, add some test code to main which calls difference and prints the result.