目录
一、动态树的核心思想
1. 什么是动态树?
动态树(以Link-Cut Tree为代表)是一种支持以下操作的数据结构:
Link(u, v):连接两个不连通的树。
Cut(u, v):切断树中的某条边。
FindRoot(u):查找节点
u
所在树的根。Path Query:对
u
到v
的路径进行统计(如最大值、求和等)。
其核心目标是在均摊对数时间复杂度内完成上述操作。
2. 核心原理:虚实链与伸展树(Splay Tree)
动态树通过以下技术实现高效操作:
虚实链划分:将树分解为若干“实链”(Preferred Path),其余为虚边。
伸展树(Splay Tree):每条实链用伸展树维护,支持快速合并与分裂。
路径压缩:通过伸展操作将频繁访问的节点移动到根附近,减少后续操作时间。
二、动态树的Java实现
1. 节点结构
每个节点保存父节点、左右子节点(在伸展树中的位置)及路径统计信息。
class DynamicTreeNode {
int value; // 节点值
int pathSum; // 路径和(根据需求定义)
DynamicTreeNode parent;
DynamicTreeNode left;
Dynamic TreeNode right;
boolean isReversed; // 懒标记:路径是否需要翻转
public DynamicTreeNode(int value) {
this.value = value;
this.pathSum = value;
this.parent = this.left = this.right = null;
this.isReversed = false;
}
}
2. 伸展操作(Splay)
将节点旋转到其所在伸展树的根,维护路径统计信息。
private void splay(DynamicTreeNode node) {
while (node.parent != null) {
DynamicTreeNode parent = node.parent;
DynamicTreeNode grandparent = parent.parent;
// 处理懒标记
pushDown(parent);
pushDown(node);
if (grandparent == null) {
// 单旋转(Zig)
if (parent.left == node) {
rotateRight(parent);
} else {
rotateLeft(parent);
}
} else {
// 双旋转(Zig-Zig或Zig-Zag)
if (grandparent.left == parent) {
if (parent.left == node) {
rotateRight(grandparent);
rotateRight(parent);
} else {
rotateLeft(parent);
rotateRight(grandparent);
}
} else {
if (parent.right == node) {
rotateLeft(grandparent);
rotateLeft(parent);
} else {
rotateRight(parent);
rotateLeft(grandparent);
}
}
}
}
update(node); // 更新统计信息
}
// 右旋(以parent为轴)
private void rotateRight(DynamicTreeNode parent) {
DynamicTreeNode node = parent.left;
parent.left = node.right;
if (node.right != null) {
node.right.parent = parent;
}
node.parent = parent.parent;
if (parent.parent != null) {
if (parent.parent.left == parent) {
parent.parent.left = node;
} else {
parent.parent.right = node;
}
}
node.right = parent;
parent.parent = node;
update(parent);
update(node);
}
// 左旋(对称操作略)
3. 虚实链切换:Access操作
将节点到根的路径变为实链,并切断其他实链。
public void access(DynamicTreeNode node) {
DynamicTreeNode last = null;
while (node != null) {
splay(node);
node.right = last; // 将右子树设为上一个处理的链
update(node);
last = node;
node = node.parent;
}
}
4. 核心操作实现
(1) 连接两个树(Link)
public void link(DynamicTreeNode child, DynamicTreeNode parent) {
access(child);
access(parent);
child.parent = parent;
parent.left = child; // 根据需求调整连接方向
update(parent);
}
(2) 切断边(Cut)
public void cut(DynamicTreeNode node) {
access(node);
if (node.left != null) {
node.left.parent = null;
node.left = null;
update(node);
}
}
(3) 查找根(Find Root)
public DynamicTreeNode findRoot(DynamicTreeNode node) {
access(node);
while (node.left != null) {
node = node.left;
}
splay(node); // 将根节点伸展到顶部
return node;
}
(4) 路径查询(Path Sum)
public int pathSum(DynamicTreeNode u, DynamicTreeNode v) {
access(u);
access(v); // 合并u和v的路径
splay(v);
return v.pathSum;
}
三、应用示例:动态连通性问题
问题描述:维护一个动态森林,支持以下操作:
连接两个节点。
查询两个节点是否连通。
Java实现:
public class DynamicConnectivity {
private Map<Integer, DynamicTreeNode> nodes = new HashMap<>();
// 判断u和v是否连通
public boolean isConnected(int uId, int vId) {
DynamicTreeNode u = nodes.get(uId);
DynamicTreeNode v = nodes.get(vId);
if (u == null || v == null) return false;
return findRoot(u) == findRoot(v);
}
// 连接u和v
public void link(int uId, int vId) {
DynamicTreeNode u = nodes.get(uId);
DynamicTreeNode v = nodes.get(vId);
if (u == null || v == null) return;
link(u, v);
}
// 创建新节点
public void addNode(int id) {
nodes.put(id, new DynamicTreeNode(id));
}
}
四、总结
动态树通过虚实链划分和伸展树技术,实现了高效的动态连接与路径查询。本文的Java实现展示了其核心操作,实际应用中需根据需求扩展路径统计逻辑(如最大值、最小值)。动态树的均摊时间复杂度为
O(log n)
,适合处理大规模动态树结构问题。