← 返回题目列表

Treap 是什么,它如何用随机优先级保持平衡?

高频 困难 第 15 / 25 题 更新于 2026/07/29
Treap平衡树随机化堆性质二叉搜索树

简化版

Treap = Tree + Heap。它对 key 满足二叉搜索树性质,对随机生成的 priority 满足堆性质。插入时先按 BST 插入,再通过旋转让 priority 恢复堆序;由于 priority 随机,树高期望为 O(log n),所以查找、插入、删除的期望复杂度都是 O(log n)。

详细版

Treap 给每个节点两个值:业务 key 用来保持 BST 有序,随机 priority 用来模拟随机建树的平衡性。以小根堆 priority 为例,父节点 priority 要小于孩子。插入新 key 时,先像普通 BST 一样插到叶子位置,再看它的 priority 是否比父节点更小;若破坏堆性质,就旋转把它提上去,直到 BST 性质和堆性质都满足。

删除时通常把目标节点旋转下沉:优先让 priority 更小的孩子旋上来,直到目标节点变成叶子再删;也可以把 priority 改成极大值再下沉。Treap 不是严格平衡树,最坏仍可能 O(n),但随机化让期望高度为 O(log n)。面试重点是双性质、旋转维护、期望复杂度和随机化风险。

完整版教学

一、Treap 的名字说明了它的本质

Treap 由 Tree 和 Heap 组成。它同时维护两套顺序:按 key 看,它是一棵二叉搜索树;按 priority 看,它是一棵堆。key 来自业务数据,priority 通常在插入时随机生成。这样做的目标是避免普通 BST 按有序数据插入时退化成链表。

每个节点:
  key      用于 BST 顺序
  priority 用于 Heap 顺序

BST 性质:
  left.key < node.key < right.key

小根堆性质:
  node.priority < child.priority

Treap 的平衡不是靠 AVL 的高度差,也不是靠红黑树的颜色,而是靠随机 priority 让树形近似随机。

二、为什么随机 priority 能带来期望平衡

如果只按 key 插入 1,2,3,4,5,普通 BST 会退化成右链,高度为 5。Treap 给每个 key 一个随机 priority,树形等价于“按 priority 从小到大决定谁先成为根”。随机 priority 打乱了有序 key 的插入影响,使每个元素都有机会成为局部根。

key:      1   2   3   4   5
priority: 50  12  80  30  60

priority 最小的 2 会更靠近根,
而不是让 key=1 因为最早插入就固定成根。

随机化分析的结论是:Treap 的期望高度为 O(log n)。这里要强调“期望”,因为如果随机数很差或被恶意构造,最坏仍可能退化到 O(n)。

三、插入如何同时维护 BST 和堆性质

插入分两步。第一步按 key 做普通 BST 插入,把新节点放到叶子;第二步检查 priority,如果新节点 priority 比父节点更小,就通过旋转把新节点提上去。旋转不会破坏 BST 中序顺序,但会改变父子高度关系,从而恢复堆性质。

插入 key=7, priority=10

      5(p=30)
        \
        9(p=40)
       /
    7(p=10)

7 的 priority 更小,需要先右旋 9,再左旋 5,使 7 上浮。

旋转的方向取决于新节点是父节点的左孩子还是右孩子。左孩子 priority 更高优先级时右旋,右孩子更高优先级时左旋。

四、插入代码框架

下面用小根堆 priority 描述。递归插入后,如果对应孩子的 priority 比当前节点更小,就旋转。

Node insert(Node root, int key) {
    if (root == null) return new Node(key, randomPriority());

    if (key < root.key) {
        root.left = insert(root.left, key);
        if (root.left.priority < root.priority) {
            root = rotateRight(root);
        }
    } else if (key > root.key) {
        root.right = insert(root.right, key);
        if (root.right.priority < root.priority) {
            root = rotateLeft(root);
        }
    }
    return root;
}

如果允许重复值,可以在节点上维护 count,避免相同 key 一直向某一侧插入。Treap 和其他 BST 一样,重复值策略必须提前定义。

五、删除为什么要旋转下沉

删除一个有两个孩子的节点时,普通 BST 会找前驱或后继替换;Treap 更常见的做法是让目标节点通过旋转向下沉,直到变成叶子再删。每次选择 priority 更小的孩子旋上来,才能保持堆性质。

删除场景处理方式原因
没有孩子直接删除不影响其他结构
只有一个孩子用孩子替代BST 和堆性质容易保持
两个孩子旋转 priority 更小的孩子上来保持堆性质并让目标下沉
delete(node):
  if node has two children:
    if left.priority < right.priority: rotateRight
    else: rotateLeft
    continue deleting old node below

这个过程的本质是把“要删除的 key”往叶子方向推,同时保持剩余节点仍是合法 Treap。

六、常见误区与追问

记忆钩子:Treap 的 key 管搜索顺序,priority 管树形高度。

  • 误区:Treap 是严格平衡树。 Treap 依靠随机化保证期望平衡,不像 AVL 那样严格限制高度差。
  • 误区:priority 可以根据 key 固定生成。 如果 priority 与 key 强相关,就可能失去随机化平衡效果。
  • 误区:旋转会破坏 BST 性质。 正确的单旋保持中序序列不变,只调整局部高度和堆序。
  • 追问:复杂度为什么说期望 O(log n)? priority 随机时树形等价于随机 BST,期望高度为对数级。
  • 追问:删除时为什么选择 priority 更小的孩子旋上来? 因为小根堆要求父节点 priority 更小,旋上更小 priority 的孩子才能维持堆性质。
  • 追问:Treap 和红黑树怎么取舍? Treap 实现短、随机化简单;红黑树最坏复杂度更稳定,更适合标准库和强确定性场景。

七、加强记忆

Treap 要记成“双规则结构”:key 满足 BST,所以能按值搜索;priority 满足堆,所以随机决定树形。插入先按 key 放到叶子,再靠旋转让 priority 上浮;删除则把目标节点旋转下沉到容易删除的位置。它的优势是实现相对简单、期望 O(log n),代价是最坏情况没有红黑树那样确定,随机数质量和重复值策略都要说清楚。