算法背景
Treap(Tree + Heap,树堆)是一种结合了二叉搜索树(BST)和堆两种数据结构特性的平衡树。每个节点除了存储键值外,还附加一个随机优先级。Treap以键值满足二叉搜索树性质(左小右大),以优先级满足堆性质(父节点优先级高于子节点)。由于优先级是随机生成的,Treap的期望高度为 $O(\log n)$,实现简单且无需复杂的旋转平衡操作。
核心思想
Treap的平衡不依赖显式旋转维护高度,而是依赖随机优先级天然形成的堆结构。插入时先按BST规则找到位置,然后通过旋转(左旋/右旋)将节点上浮至满足堆性质的位置。删除则通过旋转将目标节点下移至叶子后移除。
Java实现
import java.util.Random;
/**
* Treap(树堆)实现
* 结合二叉搜索树与堆的性质,通过随机优先级实现期望O(log n)的平衡
*/
public class Treap {
private static final Random RANDOM = new Random();
/**
* 节点类
*/
static class Node {
int key; // 键值(BST排序依据)
int priority; // 随机优先级(堆排序依据)
int size; // 以该节点为根的子树大小
int count; // 相同键值的计数
Node left, right; // 左右子节点
Node(int key) {
this.key = key;
this.priority = RANDOM.nextInt();
this.size = 1;
this.count = 1;
}
}
private Node root;
/**
* 更新节点size字段
*/
private void updateSize(Node node) {
if (node != null) {
node.size = node.count + getSize(node.left) + getSize(node.right);
}
}
/**
* 获取子树大小
*/
private int getSize(Node node) {
return node == null ? 0 : node.size;
}
/**
* 右旋:将左子节点提升为当前子树的根
* y x
* / \ -> / \
* x c a y
* / \ / \
* a b b c
*/
private Node rotateRight(Node y) {
Node x = y.left;
Node b = x.right;
x.right = y;
y.left = b;
updateSize(y);
updateSize(x);
return x;
}
/**
* 左旋:将右子节点提升为当前子树的根
* x y
* / \ -> / \
* a y x c
* / \ / \
* b c a b
*/
private Node rotateLeft(Node x) {
Node y = x.right;
Node b = y.left;
y.left = x;
x.right = b;
updateSize(x);
updateSize(y);
return y;
}
/**
* 插入键值
* 先按BST找到位置,再通过旋转维护堆性质
*/
public void insert(int key) {
root = insert(root, key);
}
private Node insert(Node node, int key) {
if (node == null) {
return new Node(key);
}
if (key == node.key) {
node.count++;
} else if (key < node.key) {
node.left = insert(node.left, key);
// 左子节点优先级更高,需要右旋上浮
if (node.left.priority > node.priority) {
node = rotateRight(node);
}
} else {
node.right = insert(node.right, key);
// 右子节点优先级更高,需要左旋上浮
if (node.right.priority > node.priority) {
node = rotateLeft(node);
}
}
updateSize(node);
return node;
}
/**
* 删除键值
* 通过旋转将目标节点下移至叶子后删除
*/
public void delete(int key) {
root = delete(root, key);
}
private Node delete(Node node, int key) {
if (node == null) {
return null;
}
if (key < node.key) {
node.left = delete(node.left, key);
} else if (key > node.key) {
node.right = delete(node.right, key);
} else {
// 找到目标节点
if (node.count > 1) {
node.count--;
} else {
// 通过旋转将当前节点下沉至叶子
if (node.left == null) {
return node.right;
} else if (node.right == null) {
return node.left;
} else if (node.left.priority > node.right.priority) {
node = rotateRight(node);
node.right = delete(node.right, key);
} else {
node = rotateLeft(node);
node.left = delete(node.left, key);
}
}
}
updateSize(node);
return node;
}
/**
* 查找键值是否存在
*/
public boolean contains(int key) {
Node cur = root;
while (cur != null) {
if (key == cur.key) return true;
cur = key < cur.key ? cur.left : cur.right;
}
return false;
}
/**
* 查找第k小的元素(1-based)
*/
public int kth(int k) {
if (k < 1 || k > getSize(root)) {
throw new IllegalArgumentException("k out of range");
}
return kth(root, k);
}
private int kth(Node node, int k) {
int leftSize = getSize(node.left);
if (k <= leftSize) {
return kth(node.left, k);
} else if (k <= leftSize + node.count) {
return node.key;
} else {
return kth(node.right, k - leftSize - node.count);
}
}
/**
* 查找键值的排名(比key小的元素个数+1)
*/
public int rank(int key) {
return rank(root, key);
}
private int rank(Node node, int key) {
if (node == null) {
return 1;
}
if (key <= node.key) {
return rank(node.left, key);
} else {
return getSize(node.left) + node.count + rank(node.right, key);
}
}
/**
* 查找前驱(小于key的最大值)
*/
public Integer predecessor(int key) {
Node cur = root;
Integer result = null;
while (cur != null) {
if (cur.key < key) {
result = cur.key;
cur = cur.right;
} else {
cur = cur.left;
}
}
return result;
}
/**
* 查找后继(大于key的最小值)
*/
public Integer successor(int key) {
Node cur = root;
Integer result = null;
while (cur != null) {
if (cur.key > key) {
result = cur.key;
cur = cur.left;
} else {
cur = cur.right;
}
}
return result;
}
/**
* 中序遍历(升序输出)
*/
public void inorder() {
inorder(root);
System.out.println();
}
private void inorder(Node node) {
if (node == null) return;
inorder(node.left);
for (int i = 0; i < node.count; i++) {
System.out.print(node.key + " ");
}
inorder(node.right);
}
/**
* 获取树的大小
*/
public int size() {
return getSize(root);
}
public static void main(String[] args) {
Treap treap = new Treap();
int[] values = {50, 30, 70, 20, 40, 60, 80, 25, 35};
System.out.println("=== 插入元素 ===");
for (int v : values) {
treap.insert(v);
System.out.println("插入 " + v + " 后,树大小: " + treap.size());
}
System.out.println("\n=== 中序遍历(升序) ===");
treap.inorder();
System.out.println("\n=== 查询操作 ===");
System.out.println("包含 40? " + treap.contains(40));
System.out.println("包含 100? " + treap.contains(100));
System.out.println("第3小: " + treap.kth(3));
System.out.println("40的排名: " + treap.rank(40));
System.out.println("30的前驱: " + treap.predecessor(30));
System.out.println("30的后继: " + treap.successor(30));
System.out.println("\n=== 删除操作 ===");
treap.delete(30);
System.out.println("删除30后中序遍历:");
treap.inorder();
System.out.println("树大小: " + treap.size());
}
}
复杂度分析
| 操作 | 平均时间复杂度 | 最坏时间复杂度 | 说明 |
|---|---|---|---|
| 插入 | $O(\log n)$ | $O(n)$ | 随机优先级期望平衡 |
| 删除 | $O(\log n)$ | $O(n)$ | 通过旋转下沉至叶子 |
| 查找 | $O(\log n)$ | $O(n)$ | BST标准查找 |
| 第k小 | $O(\log n)$ | $O(n)$ | 利用子树size字段 |
| 前驱/后继 | $O(\log n)$ | $O(n)$ | 单次遍历 |
空间复杂度为 $O(n)$,每个节点存储键值、优先级、计数和子树大小。
算法优势与适用场景
优势:
– 实现简洁,仅需左旋/右旋两种操作
– 无需存储额外平衡因子或颜色信息
– 支持区间查询、第k小等扩展操作
适用场景:
– 需要动态维护有序集合的问题
– 在线算法中需要快速插入删除的场景
– 作为其他高级数据结构(如线段树套Treap)的基础组件