Algorithm Search⌘K

Proofs: Zip tree

These are the human proofs of Zip tree, at full length.

Zip and unzip keep the zip-tree order full

This proof shows: correctness.

Say that a node uu beats a node vv when u.rank>v.ranku.rank > v.rank, or u.rank=v.ranku.rank = v.rank and u.key<v.keyu.key < v.key. Keys are distinct, so this is a strict total order on the nodes. A tree is a zip tree when:

  1. (Z1) it is a binary search tree on its keys;
  2. (Z2) every node beats its children.

For a left child cc of uu, c.key<u.keyc.key < u.key, so (Z2) asks c.rank<u.rankc.rank < u.rank; for a right child, c.rank≤u.rankc.rank \le u.rank. This is the rank rule checked in the code. By (Z2) and transitivity, every node beats all of its proper descendants. So a zip tree is a treap whose priority is the pair (rank,−key)(rank, -key), and for a given set of keys and ranks there is exactly one zip tree: the root must be the node that beats all others, and the two subtrees are the zip trees of the smaller and of the larger keys.

Lemma 1 (UNZIP). Let tt be a zip tree and kk a key not in tt. UNZIP(t,k)(t, k) returns zip trees PP and QQ whose nodes are those of tt, with the keys less than kk in PP and the greater keys in QQ.

Proof. Let t1,t2,…,tmt_1, t_2, \ldots, t_m be the nodes visited by the loop of line L6; they form the search path of kk in tt, from the root down. A node tjt_j with tj.key<kt_j.key < k goes to PP, and all of its left subtree has keys less than tj.key<kt_j.key < k, so the whole left subtree goes along unchanged. A node with tj.key>kt_j.key > k goes to QQ with its right subtree. Every node of tt is on the path or in such a subtree, because the path leaves each visited node through the other side. So the keys are divided as claimed.

Consider the nodes p1,p2,…p_1, p_2, \ldots of the path that go to PP, in the order visited. The loop makes pj+1p_{j+1} the right child of pjp_j (line L6), and line L7 ends the spine with nil. In tt, after visiting pjp_j the path goes right, so pj+1p_{j+1} lies in the right subtree of pjp_j: its key is larger, and pjp_j beats it. So the new link keeps (Z1) and (Z2). All of the right subtree of pjp_j in PP consists of nodes from its old right subtree with keys less than kk, so (Z1) holds at pjp_j. The left subtrees are unchanged. So PP is a zip tree with root p1p_1, and QQ is a zip tree by the mirror argument. □\square

Lemma 2 (ZIP). Let aa and bb be zip trees with every key of aa less than every key of bb. ZIP(a,b)(a, b) returns a zip tree on the nodes of aa and bb.

Proof. Lines L9 and L10 are the loop form of the recursion

ZIP(a,b)={bif a=nil,aif b=nil,a with a.right←ZIP(a.right,b)if a.rank≥b.rank,b with b.left←ZIP(a,b.left)otherwise:\text{ZIP}(a, b) = \begin{cases} b & \text{if } a = \text{nil},\\ a & \text{if } b = \text{nil},\\ a \text{ with } a.right \leftarrow \text{ZIP}(a.right, b) & \text{if } a.rank \ge b.rank,\\ b \text{ with } b.left \leftarrow \text{ZIP}(a, b.left) & \text{otherwise}: \end{cases}

each pass of the loop hangs the root of one call at the place where the previous call puts its result. Since a.key<b.keya.key < b.key, the test a.rank≥b.ranka.rank \ge b.rank says exactly that aa beats bb. We prove the recursion by induction on the number of nodes. In the third case, the root aa keeps its left subtree. By induction, ZIP(a.right,b)\text{ZIP}(a.right, b) is a zip tree on the nodes of a.righta.right and bb, all with keys greater than a.keya.key, so (Z1) holds at aa. Its root is a.righta.right or bb; aa beats a.righta.right by (Z2), and aa beats bb, so (Z2) holds at aa. The fourth case is the mirror image, with bb beating aa. □\square

Insertion. Line L1 returns if kk is present; otherwise xx is a new node with key kk. Line L2 walks down the search path of kk while the current node beats xx; let curcur be the node where it stops (possibly nil) and parentparent the node before it. Every node above curcur on the path beats xx. If curcur is a node, it does not beat xx, so xx beats curcur, and by transitivity xx beats every node of the subtree of curcur. By Lemma 1, line L3 makes the left subtree of xx a zip tree on the keys of that subtree less than kk, and the right subtree a zip tree on the greater keys; so (Z1) and (Z2) hold at xx. Line L4 puts xx in the place of curcur. The subtree of curcur held exactly the keys of the tree in the interval of that place, and kk is in this interval, since the search path of kk passes there; so (Z1) holds at parentparent and above. parentparent beats xx, so (Z2) holds there. Nothing else changes.

Deletion. Line L8 finds the node xx with key kk, or returns. By Lemma 2, ZIP of its two subtrees is a zip tree on all the nodes below xx, with the same set of keys apart from kk. It takes the place of xx; its keys lie in the interval of that place, and the parent of xx beats all of them, because they were descendants. So (Z1) and (Z2) hold.

Theorem. Starting from the empty tree, after any sequence of insertions and deletions the tree is a zip tree on exactly the keys inserted and not deleted, and a search returns true exactly for these keys. Its shape depends only on the keys and their ranks, not on the order of the operations.

Proof. The empty tree is a zip tree. The two cases above keep (Z1) and (Z2) and change the key set as stated. The search is correct by (Z1). The shape is the unique zip tree for the keys and ranks, as shown at the start. ■\blacksquare

Back to Zip tree