Code

avl.go — the skeleton

Create avl.go. The usual skeleton; the bodies are yours.

Most of this file is lesson 11's code carried across unchangedGet, Keys, Range, Min, Max are identical with *node replaced by *avlNode. Rotations change the shape, not the meaning. Copy them and do not feel like you are cheating: that is the observation that an AVL tree is a BST.

Genuinely new: five functions — height, balance, fix, the two rotations, and rebalance.

Worth noting:

  • height must be nil-safe. An absent subtree has height 0. Half of all AVL bugs are a missing nil check right here, and they show up as "sometimes unbalanced" rather than as a crash.
  • fix runs on two nodes after a rotation, and the order matters. The one that moved down first, then the one that moved up — the second one's height depends on the first.
  • An equal key does not rebalance. Put on an existing key only replaces the record; the shape did not change, so there is nothing to repair.
  • Rotations are not comparisons. c.Hit() fires only when two keys are compared. Count rotations there too and step 6 stops working — and step 6 is precisely about what the counter cannot see.
  • Height() is O(1) here — it returns root.h. In lesson 11 it had to walk the whole tree. That is a free dividend from the field you just added.

Insertion has to be recursive. Lesson 11's loop over &(*cur).left will not do: the repair happens on the way back up, and a loop has no way back up.

Gotcha

rebalance returns the new subtree root, and the caller has to take that value:

n.left = insert(n.left)   // correct — the subtree may have a new root
insert(n.left)            // wrong — a rotation happened and was thrown away

The second version compiles and reports no error. Len() climbs on every call, because the counter is incremented where the leaf is created — but the leaf itself is thrown away, so Get cannot find the key. Measured: the very first test fails with Get("00000"): ok=false.

A silent no-op that still increments the count is a nastier failure than a crash, and it is the reason the first thing avl_test.go checks is that every key stored can be found again.