要讓搜尋樹不長歪,AVL 和紅黑樹的辦法是嚴格記錄高度或顏色——有點麻煩。這頁的兩種樹改用更「取巧」的辦法: Treap 給每個節點擲一個隨機號碼,強迫樹依隨機順序長,靠運氣讓它大機率保持平衡,程式碼卻簡單很多; Splay 樹乾脆不管平不平衡,而是每次你查過某個節點,就順手把它一路轉到樹頂——於是最近常用的東西 永遠在最上面、下次找超快,像瀏覽器把常看的分頁放在手邊。兩者都放棄了「保證」平衡,換來更簡單、或更貼近 使用習慣的設計。
兩者都靠旋轉維持效率,但換取平衡的手段完全不同:Treap 給每個節點一個隨機優先級,插入時額外維持 「優先級 max-heap」性質,靠隨機性換取期望 O(log n)(不需要像 AVL/紅黑樹那樣追蹤高度或顏色); Splay Tree 則不維護任何額外平衡資訊,而是每次 insert/search 後把剛存取的節點旋轉伸展到根, 靠「常存取節點靠近根部」的局部性換取均攤 O(log n),特別適合存取頻率不均勻的工作負載。
insert(value) 先照 BST 規則逐層比較走到 node == null,建立新節點時額外指派一個 priority (本頁用固定種子 LCG 高位元取樣,決定性重播用;實務 Treap 應使用真隨機亂數,priority 隨機才能保證期望 O(log n),固定種子只是為了讓每次示範都重現相同的旋轉步驟);接著從新節點回溯往上, 只要子節點 priority 大於自身 priority 就違反了「priority 的 max-heap 性質」,旋轉一次把子節點換到 父節點的位置,直到 heap 性質恢復或已升到 root。同時維持「BST 鍵序」與「priority 堆積序」兩個不變量。
insert(key)/search(key) 一樣先做標準 BST 定位(找到節點,或 insert 走到 null 建立新節點; search 未找到則改伸展最後訪問節點);找到目標節點 x 後呼叫 splay(x),依 x 與父親 p、祖父 g 的相對位置反覆旋轉直到 x 成為新 root,分三種情形:Zig(g 不存在,父親就是 root,對 p 單旋一次)、 Zig-Zig(x 與 p 同側,即兩者都是左子或都是右子,先旋轉祖父 g 再旋轉父 p,順序是關鍵)、 Zig-Zag(x 與 p 異側,先旋轉父 p 再旋轉祖父 g,兩次旋轉方向相反)。