3-5 Splay树
Splay树(Splay Tree)
Splay树(Splay Tree)是一种自调整二叉搜索树(Self-Adjusting Binary Search Tree),由 Daniel Sleator 和 Robert Tarjan 于 1985 年提出。它的核心思想是:每次访问(搜索、插入或删除)一个节点后,通过一系列旋转操作将该节点移动到树的根节点位置,这个过程称为"伸展"(Splay)。
Splay 树不需要像 AVL 树或红黑树那样维护额外的平衡信息(如高度、颜色等),而是通过"最近访问的节点快速可达"这一局部性原理(Locality of Reference)来获得良好的摊还时间复杂度(Amortized Time Complexity)——所有操作的摊还时间复杂度均为 O(log n)。
Splay 树在实际工程中有着广泛的应用:
- GCC STL 的
rope数据结构(长字符串操作) - Windows NT 内核中的内存管理
- JavaScript 引擎 V8 的字符串拼接优化
- 各种缓存(Cache)和垃圾回收器(Garbage Collector)的实现
Splay操作
Splay 操作是 Splay 树的核心。在每次搜索、插入或删除操作之后,被访问的节点会通过旋转被提升到根节点位置。这种"最近使用的元素靠近根部"的特性使得频繁访问的热点数据能够被快速定位。
根据被伸展节点 x、其父节点 p 和祖父节点 g 的相对位置关系,Splay 操作分为三种情况:
1. Zig 情况(单旋转)
当节点 x 的父节点 p 是根节点时,执行一次旋转即可将 x 提升到根。
Zig(x 是 p 的左子节点)右旋 p:
p x
/ \ / \
x C => A p
/ \ / \
A B B C
Zag(x 是 p 的右子节点)左旋 p:
p x
/ \ / \
A x => p C
/ \ / \
B C A B
2. Zig-Zig 情况(同向双旋转)
当节点 x 和其父节点 p 同为各自父节点的左子节点,或同为右子节点时,先旋转祖父节点 g,再旋转父节点 p。
Zig-Zig(x 和 p 都是左子节点):
g x
/ \ / \
p D A p
/ \ => / \
x C B g
/ \ / \
A B C D
先右旋 g,再右旋 p(而不是连续两次右旋 p)
3. Zig-Zag 情况(反向双旋转)
当节点 x 是父节点 p 的左子节点,而 p 是祖父节点 g 的右子节点(或对称情况)时,先旋转 p,再旋转 g。
Zig-Zag(x 是 p 的左子节点,p 是 g 的右子节点):
g x
/ \ / \
A p g p
/ \ => / \ / \
x D A B C D
/ \
B C
先右旋 p,再左旋 g
Zig-Zig 和 Zig-Zag 的关键区别在于:Zig-Zig 是同方向两次旋转(先旋祖父,再旋父),Zig-Zag 是反方向两次旋转(先旋父,再旋祖父)。Zig-Zig 的这种"先祖父后父亲"的顺序能够更有效地降低树高,这是 Splay 树摊还分析的核心。
节点定义
Splay 树的节点在普通二叉搜索树节点的基础上需要增加一个父节点(parent)指针,因为 Splay 操作需要沿着父指针向上遍历。
C++ 节点定义
struct Node {
int data;
Node* left;
Node* right;
Node* parent;
// constructor
Node(int val) : data(val), left(nullptr), right(nullptr), parent(nullptr) {}
};
与 AVL 树不同,Splay 树不需要额外存储高度或平衡因子信息。父指针是 Splay 操作向上遍历的基础。
C 节点定义
#include <stdlib.h>
typedef struct Node {
int data;
struct Node* left;
struct Node* right;
struct Node* parent;
} Node;
// create a new node with given value
Node* createNode(int val) {
Node* node = (Node*)malloc(sizeof(Node));
node->data = val;
node->left = NULL;
node->right = NULL;
node->parent = NULL;
return node;
}
C 语言使用 typedef 定义结构体,并通过 malloc 动态分配内存。
Python 节点定义
class Node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
self.parent = None
Python 版本最为简洁,无需手动管理内存和指针。
Go 节点定义
package main
type Node struct {
Data int
Left *Node
Right *Node
Parent *Node
}
// create a new node with given value
func newNode(val int) *Node {
return &Node{
Data: val,
}
}
Go 使用结构体指针实现节点间的引用关系,无需手动管理内存,垃圾回收器自动处理。
旋转与Splay操作
旋转(Rotation)是 Splay 树的基本操作。左旋和右旋改变了节点之间的父子关系,但始终维护二叉搜索树的性质:中序遍历结果不变。
右旋(Right Rotation)
右旋节点 y,将其左子节点 x 提升为新的父节点。
左旋(Left Rotation)
左旋节点 x,将其右子节点 y 提升为新的父节点。
Splay 操作
Splay 操作从被访问的节点开始,根据当前节点、父节点和祖父节点的关系,反复执行 Zig、Zig-Zig 或 Zig-Zag 旋转,直到该节点成为根节点。
C++ 实现
class SplayTree {
private:
Node* root;
// right rotate around node y
void rightRotate(Node* y) {
Node* x = y->left;
y->left = x->right;
if (x->right != nullptr)
x->right->parent = y;
x->parent = y->parent;
if (y->parent == nullptr)
root = x;
else if (y == y->parent->left)
y->parent->left = x;
else
y->parent->right = x;
x->right = y;
y->parent = x;
}
// left rotate around node x
void leftRotate(Node* x) {
Node* y = x->right;
x->right = y->left;
if (y->left != nullptr)
y->left->parent = x;
y->parent = x->parent;
if (x->parent == nullptr)
root = y;
else if (x == x->parent->left)
x->parent->left = y;
else
x->parent->right = y;
y->left = x;
x->parent = y;
}
// splay node to root
void splay(Node* node) {
while (node->parent != nullptr) {
Node* parent = node->parent;
Node* grandparent = parent->parent;
if (grandparent == nullptr) {
// Zig case: parent is root
if (node == parent->left)
rightRotate(parent);
else
leftRotate(parent);
} else if (node == parent->left && parent == grandparent->left) {
// Zig-Zig case (left-left)
rightRotate(grandparent);
rightRotate(parent);
} else if (node == parent->right && parent == grandparent->right) {
// Zig-Zig case (right-right)
leftRotate(grandparent);
leftRotate(parent);
} else if (node == parent->right && parent == grandparent->left) {
// Zig-Zag case (left-right)
leftRotate(parent);
rightRotate(grandparent);
} else {
// Zig-Zag case (right-left)
rightRotate(parent);
leftRotate(grandparent);
}
}
}
public:
SplayTree() : root(nullptr) {}
// insert, search, delete will be added later
};
rightRotate 将左子节点提升为父节点,leftRotate 将右子节点提升为父节点。splay 函数根据三种情况(Zig、Zig-Zig、Zig-Zag)反复旋转,直到目标节点到达根位置。
C 实现
typedef struct {
Node* root;
} SplayTree;
// right rotate around node y
void rightRotate(SplayTree* tree, Node* y) {
Node* x = y->left;
y->left = x->right;
if (x->right != NULL)
x->right->parent = y;
x->parent = y->parent;
if (y->parent == NULL)
tree->root = x;
else if (y == y->parent->left)
y->parent->left = x;
else
y->parent->right = x;
x->right = y;
y->parent = x;
}
// left rotate around node x
void leftRotate(SplayTree* tree, Node* x) {
Node* y = x->right;
x->right = y->left;
if (y->left != NULL)
y->left->parent = x;
y->parent = x->parent;
if (x->parent == NULL)
tree->root = y;
else if (x == x->parent->left)
x->parent->left = y;
else
x->parent->right = y;
y->left = x;
x->parent = y;
}
// splay node to root
void splay(SplayTree* tree, Node* node) {
while (node->parent != NULL) {
Node* parent = node->parent;
Node* grandparent = parent->parent;
if (grandparent == NULL) {
if (node == parent->left)
rightRotate(tree, parent);
else
leftRotate(tree, parent);
} else if (node == parent->left && parent == grandparent->left) {
rightRotate(tree, grandparent);
rightRotate(tree, parent);
} else if (node == parent->right && parent == grandparent->right) {
leftRotate(tree, grandparent);
leftRotate(tree, parent);
} else if (node == parent->right && parent == grandparent->left) {
leftRotate(tree, parent);
rightRotate(tree, grandparent);
} else {
rightRotate(tree, parent);
leftRotate(tree, grandparent);
}
}
}
C 版本通过传递 SplayTree* 指针来访问和修改根节点,逻辑与 C++ 版本完全一致。
Python 实现
class SplayTree:
def __init__(self):
self.root = None
def _right_rotate(self, y):
x = y.left
y.left = x.right
if x.right is not None:
x.right.parent = y
x.parent = y.parent
if y.parent is None:
self.root = x
elif y == y.parent.left:
y.parent.left = x
else:
y.parent.right = x
x.right = y
y.parent = x
def _left_rotate(self, x):
y = x.right
x.right = y.left
if y.left is not None:
y.left.parent = x
y.parent = x.parent
if x.parent is None:
self.root = y
elif x == x.parent.left:
x.parent.left = y
else:
x.parent.right = y
y.left = x
x.parent = y
def _splay(self, node):
while node.parent is not None:
parent = node.parent
grandparent = parent.parent
if grandparent is None:
# Zig case
if node == parent.left:
self._right_rotate(parent)
else:
self._left_rotate(parent)
elif node == parent.left and parent == grandparent.left:
# Zig-Zig (left-left)
self._right_rotate(grandparent)
self._right_rotate(parent)
elif node == parent.right and parent == grandparent.right:
# Zig-Zig (right-right)
self._left_rotate(grandparent)
self._left_rotate(parent)
elif node == parent.right and parent == grandparent.left:
# Zig-Zag (left-right)
self._left_rotate(parent)
self._right_rotate(grandparent)
else:
# Zig-Zag (right-left)
self._right_rotate(parent)
self._left_rotate(grandparent)
Python 使用下划线前缀 _ 表示私有方法,结构清晰。
Go 实现
type SplayTree struct {
root *Node
}
// right rotate around node y
func (t *SplayTree) rightRotate(y *Node) {
x := y.Left
y.Left = x.Right
if x.Right != nil {
x.Right.Parent = y
}
x.Parent = y.Parent
if y.Parent == nil {
t.root = x
} else if y == y.Parent.Left {
y.Parent.Left = x
} else {
y.Parent.Right = x
}
x.Right = y
y.Parent = x
}
// left rotate around node x
func (t *SplayTree) leftRotate(x *Node) {
y := x.Right
x.Right = y.Left
if y.Left != nil {
y.Left.Parent = x
}
y.Parent = x.Parent
if x.Parent == nil {
t.root = y
} else if x == x.Parent.Left {
x.Parent.Left = y
} else {
x.Parent.Right = y
}
y.Left = x
x.Parent = y
}
// splay node to root
func (t *SplayTree) splay(node *Node) {
for node.Parent != nil {
parent := node.Parent
grandparent := parent.Parent
if grandparent == nil {
// Zig case
if node == parent.Left {
t.rightRotate(parent)
} else {
t.leftRotate(parent)
}
} else if node == parent.Left && parent == grandparent.Left {
// Zig-Zig (left-left)
t.rightRotate(grandparent)
t.rightRotate(parent)
} else if node == parent.Right && parent == grandparent.Right {
// Zig-Zig (right-right)
t.leftRotate(grandparent)
t.leftRotate(parent)
} else if node == parent.Right && parent == grandparent.Left {
// Zig-Zag (left-right)
t.leftRotate(parent)
t.rightRotate(grandparent)
} else {
// Zig-Zag (right-left)
t.rightRotate(parent)
t.leftRotate(grandparent)
}
}
}
Go 版本使用导出字段名(首字母大写)和方法的接收者模式,逻辑与 C++/C/Python 版本完全一致。
插入操作
Splay 树的插入分为两步:首先按照标准二叉搜索树的方式找到插入位置并插入新节点,然后将新插入的节点 Splay 到根节点。这保证了最近插入的元素始终在根部附近,便于后续访问。
C++ 插入操作
void insert(int val) {
Node* newNode = new Node(val);
if (root == nullptr) {
root = newNode;
return;
}
// standard BST insert
Node* curr = root;
Node* parent = nullptr;
while (curr != nullptr) {
parent = curr;
if (val < curr->data)
curr = curr->left;
else if (val > curr->data)
curr = curr->right;
else {
// duplicate found, splay existing node
splay(curr);
delete newNode;
return;
}
}
newNode->parent = parent;
if (val < parent->data)
parent->left = newNode;
else
parent->right = newNode;
// splay the new node to root
splay(newNode);
}
插入过程首先沿 BST 规则找到位置,如果发现重复值则直接 Splay 已有节点。新节点插入后立即 Splay 到根,确保它位于树的最顶端。
C 插入操作
void insert(SplayTree* tree, int val) {
Node* newNode = createNode(val);
if (tree->root == NULL) {
tree->root = newNode;
return;
}
// standard BST insert
Node* curr = tree->root;
Node* parent = NULL;
while (curr != NULL) {
parent = curr;
if (val < curr->data)
curr = curr->left;
else if (val > curr->data)
curr = curr->right;
else {
// duplicate found, splay existing node
splay(tree, curr);
free(newNode);
return;
}
}
newNode->parent = parent;
if (val < parent->data)
parent->left = newNode;
else
parent->right = newNode;
// splay the new node to root
splay(tree, newNode);
}
C 版本使用 free 释放重复节点的内存,通过 SplayTree* 指针操作树结构。
Python 插入操作
def insert(self, val):
new_node = Node(val)
if self.root is None:
self.root = new_node
return
# standard BST insert
curr = self.root
parent = None
while curr is not None:
parent = curr
if val < curr.data:
curr = curr.left
elif val > curr.data:
curr = curr.right
else:
# duplicate found, splay existing node
self._splay(curr)
return
new_node.parent = parent
if val < parent.data:
parent.left = new_node
else:
parent.right = new_node
# splay the new node to root
self._splay(new_node)
Python 版本无需手动释放内存,代码更加简洁。
Go 插入操作
func (t *SplayTree) insert(val int) {
newNode := &Node{Data: val}
if t.root == nil {
t.root = newNode
return
}
// standard BST insert
curr := t.root
var parent *Node
for curr != nil {
parent = curr
if val < curr.Data {
curr = curr.Left
} else if val > curr.Data {
curr = curr.Right
} else {
// duplicate found, splay existing node
t.splay(curr)
return
}
}
newNode.Parent = parent
if val < parent.Data {
parent.Left = newNode
} else {
parent.Right = newNode
}
// splay the new node to root
t.splay(newNode)
}
Go 版本使用 &Node{Data: val} 创建节点,垃圾回收器自动管理内存,无需手动释放。
搜索操作
Splay 树的搜索同样分两步:首先按照 BST 的方式搜索目标值,如果找到则将目标节点 Splay 到根;如果未找到则将搜索路径上最后一个访问的节点 Splay 到根。这种"搜索后伸展"的行为使得频繁搜索的元素自动被提升到靠近根的位置。
C++ 搜索操作
bool search(int val) {
if (root == nullptr) return false;
Node* curr = root;
Node* last = nullptr;
while (curr != nullptr) {
last = curr;
if (val < curr->data)
curr = curr->left;
else if (val > curr->data)
curr = curr->right;
else {
// found, splay to root
splay(curr);
return true;
}
}
// not found, splay last accessed node
splay(last);
return false;
}
搜索到目标后返回 true 并将目标节点 Splay 到根。未找到时也执行 Splay 操作(对最后访问的节点),这一步是可选的但能保持树的良好形态。
C 搜索操作
int search(SplayTree* tree, int val) {
if (tree->root == NULL) return 0;
Node* curr = tree->root;
Node* last = NULL;
while (curr != NULL) {
last = curr;
if (val < curr->data)
curr = curr->left;
else if (val > curr->data)
curr = curr->right;
else {
splay(tree, curr);
return 1;
}
}
splay(tree, last);
return 0;
}
C 版本使用 int 返回值(1 表示找到,0 表示未找到)。
Python 搜索操作
def search(self, val):
if self.root is None:
return False
curr = self.root
last = None
while curr is not None:
last = curr
if val < curr.data:
curr = curr.left
elif val > curr.data:
curr = curr.right
else:
self._splay(curr)
return True
self._splay(last)
return False
搜索成功后,目标节点会被 Splay 到根。验证这一点只需检查 self.root.data 是否等于搜索值。
Go 搜索操作
func (t *SplayTree) search(val int) bool {
if t.root == nil {
return false
}
curr := t.root
var last *Node
for curr != nil {
last = curr
if val < curr.Data {
curr = curr.Left
} else if val > curr.Data {
curr = curr.Right
} else {
// found, splay to root
t.splay(curr)
return true
}
}
// not found, splay last accessed node
t.splay(last)
return false
}
Go 版本返回 bool 类型表示是否找到目标值,逻辑与其他语言版本完全一致。
删除操作
Splay 树的删除策略十分巧妙:首先将目标节点 Splay 到根,然后处理其左右子树。如果左子树为空,直接用右子树替代;如果右子树为空,直接用左子树替代;如果两者都不为空,则找到左子树中的最大节点(或右子树中的最小节点),将其 Splay 到左子树的根部(此时该节点没有右子节点),然后将右子树挂接到该节点上。
C++ 删除操作
void deleteNode(int val) {
if (root == nullptr) return;
// splay the target to root
search(val);
if (root->data != val) return; // not found
Node* toDelete = root;
if (root->left == nullptr) {
// no left subtree, right child becomes root
root = root->right;
if (root != nullptr)
root->parent = nullptr;
} else if (root->right == nullptr) {
// no right subtree, left child becomes root
root = root->left;
if (root != nullptr)
root->parent = nullptr;
} else {
// both subtrees exist
// detach right subtree temporarily
Node* rightSub = root->right;
rightSub->parent = nullptr;
// left subtree becomes new root
root = root->left;
root->parent = nullptr;
// find max in left subtree and splay to root of left subtree
Node* maxLeft = root;
while (maxLeft->right != nullptr)
maxLeft = maxLeft->right;
splay(maxLeft);
// attach right subtree
root->right = rightSub;
rightSub->parent = root;
}
delete toDelete;
}
删除的关键步骤:先将目标 Splay 到根,使删除操作只涉及根节点。然后将左子树的最大值 Splay 到左子树根部(此时它没有右子节点),就可以直接将右子树挂接上去,完成合并。
C 删除操作
void deleteNode(SplayTree* tree, int val) {
if (tree->root == NULL) return;
// splay the target to root
search(tree, val);
if (tree->root->data != val) return; // not found
Node* toDelete = tree->root;
if (tree->root->left == NULL) {
tree->root = tree->root->right;
if (tree->root != NULL)
tree->root->parent = NULL;
} else if (tree->root->right == NULL) {
tree->root = tree->root->left;
if (tree->root != NULL)
tree->root->parent = NULL;
} else {
Node* rightSub = tree->root->right;
rightSub->parent = NULL;
tree->root = tree->root->left;
tree->root->parent = NULL;
// find max in left subtree and splay
Node* maxLeft = tree->root;
while (maxLeft->right != NULL)
maxLeft = maxLeft->right;
splay(tree, maxLeft);
tree->root->right = rightSub;
rightSub->parent = tree->root;
}
free(toDelete);
}
C 版本使用 free 释放被删除节点的内存。
Python 删除操作
def delete(self, val):
if self.root is None:
return
# splay the target to root
self.search(val)
if self.root.data != val:
return # not found
to_delete = self.root
if self.root.left is None:
self.root = self.root.right
if self.root is not None:
self.root.parent = None
elif self.root.right is None:
self.root = self.root.left
if self.root is not None:
self.root.parent = None
else:
right_sub = self.root.right
right_sub.parent = None
self.root = self.root.left
self.root.parent = None
# find max in left subtree and splay
max_left = self.root
while max_left.right is not None:
max_left = max_left.right
self._splay(max_left)
self.root.right = right_sub
right_sub.parent = self.root
Python 版本无需手动释放内存,垃圾回收器自动处理。
Go 删除操作
func (t *SplayTree) delete(val int) {
if t.root == nil {
return
}
// splay the target to root
t.search(val)
if t.root.Data != val {
return // not found
}
toDelete := t.root
if t.root.Left == nil {
// no left subtree, right child becomes root
t.root = t.root.Right
if t.root != nil {
t.root.Parent = nil
}
} else if t.root.Right == nil {
// no right subtree, left child becomes root
t.root = t.root.Left
if t.root != nil {
t.root.Parent = nil
}
} else {
// both subtrees exist
// detach right subtree temporarily
rightSub := t.root.Right
rightSub.Parent = nil
// left subtree becomes new root
t.root = t.root.Left
t.root.Parent = nil
// find max in left subtree and splay to root of left subtree
maxLeft := t.root
for maxLeft.Right != nil {
maxLeft = maxLeft.Right
}
t.splay(maxLeft)
// attach right subtree
t.root.Right = rightSub
rightSub.Parent = t.root
}
_ = toDelete // Go's garbage collector handles cleanup
}
Go 版本利用垃圾回收器自动回收被删除节点的内存,使用 _ = toDelete 明确标识不再使用的变量。
完整实现
下面提供 Splay 树的完整可运行实现,包括插入、搜索、删除和中序遍历。演示流程为:依次插入 10, 20, 30, 40, 50,搜索 30(观察其成为根),删除 30,最后输出中序遍历结果。
C++ 完整实现
#include <iostream>
using namespace std;
struct Node {
int data;
Node* left;
Node* right;
Node* parent;
Node(int val) : data(val), left(nullptr), right(nullptr), parent(nullptr) {}
};
class SplayTree {
private:
Node* root;
void rightRotate(Node* y) {
Node* x = y->left;
y->left = x->right;
if (x->right != nullptr)
x->right->parent = y;
x->parent = y->parent;
if (y->parent == nullptr)
root = x;
else if (y == y->parent->left)
y->parent->left = x;
else
y->parent->right = x;
x->right = y;
y->parent = x;
}
void leftRotate(Node* x) {
Node* y = x->right;
x->right = y->left;
if (y->left != nullptr)
y->left->parent = x;
y->parent = x->parent;
if (x->parent == nullptr)
root = y;
else if (x == x->parent->left)
x->parent->left = y;
else
x->parent->right = y;
y->left = x;
x->parent = y;
}
void splay(Node* node) {
while (node->parent != nullptr) {
Node* parent = node->parent;
Node* grandparent = parent->parent;
if (grandparent == nullptr) {
if (node == parent->left)
rightRotate(parent);
else
leftRotate(parent);
} else if (node == parent->left && parent == grandparent->left) {
rightRotate(grandparent);
rightRotate(parent);
} else if (node == parent->right && parent == grandparent->right) {
leftRotate(grandparent);
leftRotate(parent);
} else if (node == parent->right && parent == grandparent->left) {
leftRotate(parent);
rightRotate(grandparent);
} else {
rightRotate(parent);
leftRotate(grandparent);
}
}
}
void inorderHelper(Node* node) {
if (node == nullptr) return;
inorderHelper(node->left);
cout << node->data << " ";
inorderHelper(node->right);
}
public:
SplayTree() : root(nullptr) {}
void insert(int val) {
Node* newNode = new Node(val);
if (root == nullptr) {
root = newNode;
return;
}
Node* curr = root;
Node* parent = nullptr;
while (curr != nullptr) {
parent = curr;
if (val < curr->data)
curr = curr->left;
else if (val > curr->data)
curr = curr->right;
else {
splay(curr);
delete newNode;
return;
}
}
newNode->parent = parent;
if (val < parent->data)
parent->left = newNode;
else
parent->right = newNode;
splay(newNode);
}
bool search(int val) {
if (root == nullptr) return false;
Node* curr = root;
Node* last = nullptr;
while (curr != nullptr) {
last = curr;
if (val < curr->data)
curr = curr->left;
else if (val > curr->data)
curr = curr->right;
else {
splay(curr);
return true;
}
}
splay(last);
return false;
}
void deleteNode(int val) {
if (root == nullptr) return;
search(val);
if (root->data != val) return;
Node* toDelete = root;
if (root->left == nullptr) {
root = root->right;
if (root != nullptr) root->parent = nullptr;
} else if (root->right == nullptr) {
root = root->left;
if (root != nullptr) root->parent = nullptr;
} else {
Node* rightSub = root->right;
rightSub->parent = nullptr;
root = root->left;
root->parent = nullptr;
Node* maxLeft = root;
while (maxLeft->right != nullptr)
maxLeft = maxLeft->right;
splay(maxLeft);
root->right = rightSub;
rightSub->parent = root;
}
delete toDelete;
}
void inorder() {
inorderHelper(root);
cout << endl;
}
int getRoot() {
return root ? root->data : -1;
}
};
int main() {
SplayTree tree;
// insert sequence
int values[] = {10, 20, 30, 40, 50};
for (int v : values) {
tree.insert(v);
cout << "Insert " << v << ", root = " << tree.getRoot() << endl;
}
cout << "Inorder after inserts: ";
tree.inorder();
// search 30, it becomes root
bool found = tree.search(30);
cout << "Search 30: " << (found ? "Found" : "Not found") << endl;
cout << "Root after search 30: " << tree.getRoot() << endl;
// delete 30
tree.deleteNode(30);
cout << "Delete 30, root = " << tree.getRoot() << endl;
cout << "Inorder after delete: ";
tree.inorder();
return 0;
}
运行该程序将输出:
Insert 10, root = 10
Insert 20, root = 20
Insert 30, root = 30
Insert 40, root = 40
Insert 50, root = 50
Inorder after inserts: 10 20 30 40 50
Search 30: Found
Root after search 30: 30
Delete 30, root = 20
Inorder after delete: 10 20 40 50
每次插入后,新插入的节点被 Splay 到根,因此根节点始终等于最后插入的值。搜索 30 后,30 被提升到根。删除 30 后,左子树中的最大值 20 成为了新的根节点。中序遍历始终是升序排列,验证了 BST 性质没有被破坏。
C 完整实现
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node* left;
struct Node* right;
struct Node* parent;
} Node;
typedef struct {
Node* root;
} SplayTree;
Node* createNode(int val) {
Node* node = (Node*)malloc(sizeof(Node));
node->data = val;
node->left = NULL;
node->right = NULL;
node->parent = NULL;
return node;
}
void rightRotate(SplayTree* tree, Node* y) {
Node* x = y->left;
y->left = x->right;
if (x->right != NULL) x->right->parent = y;
x->parent = y->parent;
if (y->parent == NULL) tree->root = x;
else if (y == y->parent->left) y->parent->left = x;
else y->parent->right = x;
x->right = y;
y->parent = x;
}
void leftRotate(SplayTree* tree, Node* x) {
Node* y = x->right;
x->right = y->left;
if (y->left != NULL) y->left->parent = x;
y->parent = x->parent;
if (x->parent == NULL) tree->root = y;
else if (x == x->parent->left) x->parent->left = y;
else x->parent->right = y;
y->left = x;
x->parent = y;
}
void splay(SplayTree* tree, Node* node) {
while (node->parent != NULL) {
Node* parent = node->parent;
Node* grandparent = parent->parent;
if (grandparent == NULL) {
if (node == parent->left) rightRotate(tree, parent);
else leftRotate(tree, parent);
} else if (node == parent->left && parent == grandparent->left) {
rightRotate(tree, grandparent);
rightRotate(tree, parent);
} else if (node == parent->right && parent == grandparent->right) {
leftRotate(tree, grandparent);
leftRotate(tree, parent);
} else if (node == parent->right && parent == grandparent->left) {
leftRotate(tree, parent);
rightRotate(tree, grandparent);
} else {
rightRotate(tree, parent);
leftRotate(tree, grandparent);
}
}
}
void insert(SplayTree* tree, int val) {
Node* newNode = createNode(val);
if (tree->root == NULL) { tree->root = newNode; return; }
Node* curr = tree->root;
Node* parent = NULL;
while (curr != NULL) {
parent = curr;
if (val < curr->data) curr = curr->left;
else if (val > curr->data) curr = curr->right;
else { splay(tree, curr); free(newNode); return; }
}
newNode->parent = parent;
if (val < parent->data) parent->left = newNode;
else parent->right = newNode;
splay(tree, newNode);
}
int search(SplayTree* tree, int val) {
if (tree->root == NULL) return 0;
Node* curr = tree->root;
Node* last = NULL;
while (curr != NULL) {
last = curr;
if (val < curr->data) curr = curr->left;
else if (val > curr->data) curr = curr->right;
else { splay(tree, curr); return 1; }
}
splay(tree, last);
return 0;
}
void deleteNode(SplayTree* tree, int val) {
if (tree->root == NULL) return;
search(tree, val);
if (tree->root->data != val) return;
Node* toDelete = tree->root;
if (tree->root->left == NULL) {
tree->root = tree->root->right;
if (tree->root != NULL) tree->root->parent = NULL;
} else if (tree->root->right == NULL) {
tree->root = tree->root->left;
if (tree->root != NULL) tree->root->parent = NULL;
} else {
Node* rightSub = tree->root->right;
rightSub->parent = NULL;
tree->root = tree->root->left;
tree->root->parent = NULL;
Node* maxLeft = tree->root;
while (maxLeft->right != NULL) maxLeft = maxLeft->right;
splay(tree, maxLeft);
tree->root->right = rightSub;
rightSub->parent = tree->root;
}
free(toDelete);
}
void inorderHelper(Node* node) {
if (node == NULL) return;
inorderHelper(node->left);
printf("%d ", node->data);
inorderHelper(node->right);
}
void inorder(SplayTree* tree) {
inorderHelper(tree->root);
printf("\n");
}
int getRoot(SplayTree* tree) {
return tree->root ? tree->root->data : -1;
}
int main() {
SplayTree tree;
tree.root = NULL;
int values[] = {10, 20, 30, 40, 50};
int n = sizeof(values) / sizeof(values[0]);
for (int i = 0; i < n; i++) {
insert(&tree, values[i]);
printf("Insert %d, root = %d\n", values[i], getRoot(&tree));
}
printf("Inorder after inserts: ");
inorder(&tree);
int found = search(&tree, 30);
printf("Search 30: %s\n", found ? "Found" : "Not found");
printf("Root after search 30: %d\n", getRoot(&tree));
deleteNode(&tree, 30);
printf("Delete 30, root = %d\n", getRoot(&tree));
printf("Inorder after delete: ");
inorder(&tree);
return 0;
}
运行该程序将输出:
Insert 10, root = 10
Insert 20, root = 20
Insert 30, root = 30
Insert 40, root = 40
Insert 50, root = 50
Inorder after inserts: 10 20 30 40 50
Search 30: Found
Root after search 30: 30
Delete 30, root = 20
Inorder after delete: 10 20 40 50
C 版本的输出结果与 C++ 版本完全一致。
Python 完整实现
class Node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
self.parent = None
class SplayTree:
def __init__(self):
self.root = None
def _right_rotate(self, y):
x = y.left
y.left = x.right
if x.right is not None:
x.right.parent = y
x.parent = y.parent
if y.parent is None:
self.root = x
elif y == y.parent.left:
y.parent.left = x
else:
y.parent.right = x
x.right = y
y.parent = x
def _left_rotate(self, x):
y = x.right
x.right = y.left
if y.left is not None:
y.left.parent = x
y.parent = x.parent
if x.parent is None:
self.root = y
elif x == x.parent.left:
x.parent.left = y
else:
x.parent.right = y
y.left = x
x.parent = y
def _splay(self, node):
while node.parent is not None:
parent = node.parent
grandparent = parent.parent
if grandparent is None:
if node == parent.left:
self._right_rotate(parent)
else:
self._left_rotate(parent)
elif node == parent.left and parent == grandparent.left:
self._right_rotate(grandparent)
self._right_rotate(parent)
elif node == parent.right and parent == grandparent.right:
self._left_rotate(grandparent)
self._left_rotate(parent)
elif node == parent.right and parent == grandparent.left:
self._left_rotate(parent)
self._right_rotate(grandparent)
else:
self._right_rotate(parent)
self._left_rotate(grandparent)
def insert(self, val):
new_node = Node(val)
if self.root is None:
self.root = new_node
return
curr = self.root
parent = None
while curr is not None:
parent = curr
if val < curr.data:
curr = curr.left
elif val > curr.data:
curr = curr.right
else:
self._splay(curr)
return
new_node.parent = parent
if val < parent.data:
parent.left = new_node
else:
parent.right = new_node
self._splay(new_node)
def search(self, val):
if self.root is None:
return False
curr = self.root
last = None
while curr is not None:
last = curr
if val < curr.data:
curr = curr.left
elif val > curr.data:
curr = curr.right
else:
self._splay(curr)
return True
self._splay(last)
return False
def delete(self, val):
if self.root is None:
return
self.search(val)
if self.root.data != val:
return
if self.root.left is None:
self.root = self.root.right
if self.root is not None:
self.root.parent = None
elif self.root.right is None:
self.root = self.root.left
if self.root is not None:
self.root.parent = None
else:
right_sub = self.root.right
right_sub.parent = None
self.root = self.root.left
self.root.parent = None
max_left = self.root
while max_left.right is not None:
max_left = max_left.right
self._splay(max_left)
self.root.right = right_sub
right_sub.parent = self.root
def inorder_helper(self, node):
if node is None:
return []
return (self.inorder_helper(node.left)
+ [node.data]
+ self.inorder_helper(node.right))
def inorder(self):
result = self.inorder_helper(self.root)
print(' '.join(map(str, result)))
def get_root(self):
return self.root.data if self.root else -1
if __name__ == '__main__':
tree = SplayTree()
for v in [10, 20, 30, 40, 50]:
tree.insert(v)
print(f"Insert {v}, root = {tree.get_root()}")
print("Inorder after inserts: ", end='')
tree.inorder()
found = tree.search(30)
print(f"Search 30: {'Found' if found else 'Not found'}")
print(f"Root after search 30: {tree.get_root()}")
tree.delete(30)
print(f"Delete 30, root = {tree.get_root()}")
print("Inorder after delete: ", end='')
tree.inorder()
运行该程序将输出:
Insert 10, root = 10
Insert 20, root = 20
Insert 30, root = 30
Insert 40, root = 40
Insert 50, root = 50
Inorder after inserts: 10 20 30 40 50
Search 30: Found
Root after search 30: 30
Delete 30, root = 20
Inorder after delete: 10 20 40 50
三个语言版本的输出结果完全一致。可以观察到 Splay 树的几个核心特征:
- 每次插入后根节点等于新插入的值 — 因为新节点被 Splay 到了根部。
- 搜索 30 后根变为 30 — 被访问的节点自动提升到根。
- 删除 30 后根变为 20 — 删除后左子树最大值 20 被提升为新根。
- 中序遍历始终有序 — Splay 操作只改变树形态,不破坏 BST 性质。
Go 完整实现
package main
import "fmt"
type Node struct {
Data int
Left *Node
Right *Node
Parent *Node
}
type SplayTree struct {
root *Node
}
func (t *SplayTree) rightRotate(y *Node) {
x := y.Left
y.Left = x.Right
if x.Right != nil {
x.Right.Parent = y
}
x.Parent = y.Parent
if y.Parent == nil {
t.root = x
} else if y == y.Parent.Left {
y.Parent.Left = x
} else {
y.Parent.Right = x
}
x.Right = y
y.Parent = x
}
func (t *SplayTree) leftRotate(x *Node) {
y := x.Right
x.Right = y.Left
if y.Left != nil {
y.Left.Parent = x
}
y.Parent = x.Parent
if x.Parent == nil {
t.root = y
} else if x == x.Parent.Left {
x.Parent.Left = y
} else {
x.Parent.Right = y
}
y.Left = x
x.Parent = y
}
func (t *SplayTree) splay(node *Node) {
for node.Parent != nil {
parent := node.Parent
grandparent := parent.Parent
if grandparent == nil {
if node == parent.Left {
t.rightRotate(parent)
} else {
t.leftRotate(parent)
}
} else if node == parent.Left && parent == grandparent.Left {
t.rightRotate(grandparent)
t.rightRotate(parent)
} else if node == parent.Right && parent == grandparent.Right {
t.leftRotate(grandparent)
t.leftRotate(parent)
} else if node == parent.Right && parent == grandparent.Left {
t.leftRotate(parent)
t.rightRotate(grandparent)
} else {
t.rightRotate(parent)
t.leftRotate(grandparent)
}
}
}
func (t *SplayTree) insert(val int) {
newNode := &Node{Data: val}
if t.root == nil {
t.root = newNode
return
}
curr := t.root
var parent *Node
for curr != nil {
parent = curr
if val < curr.Data {
curr = curr.Left
} else if val > curr.Data {
curr = curr.Right
} else {
t.splay(curr)
return
}
}
newNode.Parent = parent
if val < parent.Data {
parent.Left = newNode
} else {
parent.Right = newNode
}
t.splay(newNode)
}
func (t *SplayTree) search(val int) bool {
if t.root == nil {
return false
}
curr := t.root
var last *Node
for curr != nil {
last = curr
if val < curr.Data {
curr = curr.Left
} else if val > curr.Data {
curr = curr.Right
} else {
t.splay(curr)
return true
}
}
t.splay(last)
return false
}
func (t *SplayTree) delete(val int) {
if t.root == nil {
return
}
t.search(val)
if t.root.Data != val {
return
}
if t.root.Left == nil {
t.root = t.root.Right
if t.root != nil {
t.root.Parent = nil
}
} else if t.root.Right == nil {
t.root = t.root.Left
if t.root != nil {
t.root.Parent = nil
}
} else {
rightSub := t.root.Right
rightSub.Parent = nil
t.root = t.root.Left
t.root.Parent = nil
maxLeft := t.root
for maxLeft.Right != nil {
maxLeft = maxLeft.Right
}
t.splay(maxLeft)
t.root.Right = rightSub
rightSub.Parent = t.root
}
}
func inorderHelper(node *Node) {
if node == nil {
return
}
inorderHelper(node.Left)
fmt.Printf("%d ", node.Data)
inorderHelper(node.Right)
}
func (t *SplayTree) inorder() {
inorderHelper(t.root)
fmt.Println()
}
func (t *SplayTree) getRoot() int {
if t.root == nil {
return -1
}
return t.root.Data
}
func main() {
tree := &SplayTree{}
values := []int{10, 20, 30, 40, 50}
for _, v := range values {
tree.insert(v)
fmt.Printf("Insert %d, root = %d\n", v, tree.getRoot())
}
fmt.Print("Inorder after inserts: ")
tree.inorder()
found := tree.search(30)
fmt.Printf("Search 30: %s\n", func() string {
if found {
return "Found"
}
return "Not found"
}())
fmt.Printf("Root after search 30: %d\n", tree.getRoot())
tree.delete(30)
fmt.Printf("Delete 30, root = %d\n", tree.getRoot())
fmt.Print("Inorder after delete: ")
tree.inorder()
}
运行该程序将输出:
Insert 10, root = 10
Insert 20, root = 20
Insert 30, root = 30
Insert 40, root = 40
Insert 50, root = 50
Inorder after inserts: 10 20 30 40 50
Search 30: Found
Root after search 30: 30
Delete 30, root = 20
Inorder after delete: 10 20 40 50
四个语言版本的输出结果完全一致。Go 使用方法接收者实现面向对象风格,for 循环替代 while,垃圾回收器自动管理内存。
Splay树的性质
时间复杂度
Splay 树的时间复杂度分析基于摊还分析(Amortized Analysis)。虽然单次操作的最坏时间复杂度为 O(n)(树可能退化为链表),但在一系列操作中,每次操作的摊还时间复杂度为 O(log n)。
这意味着,虽然偶尔会有一次较慢的操作,但总体上 Splay 树保证了与平衡树相当的效率。其理论保证由势函数方法(Potential Method)给出,核心结论是:对于 n 个节点的 Splay 树,任意 m 次操作的总时间复杂度为 O(m log n)。
| 操作 | 摊还时间复杂度 | 最坏时间复杂度 |
|---|---|---|
| 搜索(Search) | O(log n) | O(n) |
| 插入(Insert) | O(log n) | O(n) |
| 删除(Delete) | O(log n) | O(n) |
| 前驱/后继(Predecessor/Successor) | O(log n) | O(n) |
| 合并(Join) | O(log n) | O(n) |
| 分裂(Split) | O(log n) | O(n) |
与 AVL 树和红黑树的对比
| 特性 | Splay 树(Splay Tree) | AVL 树(AVL Tree) | 红黑树(Red-Black Tree) |
|---|---|---|---|
| 平衡方式 | 自调整,无需额外信息 | 严格平衡,高度差不超过 1 | 近似平衡,最长路径不超过最短路径的 2 倍 |
| 额外存储 | 无(不需要平衡因子或颜色) | 每节点 1 个整数(高度或平衡因子) | 每节点 1 bit(颜色) |
| 查找最坏时间 | O(n) | O(log n) | O(log n) |
| 查找摊还时间 | O(log n) | O(log n) | O(log n) |
| 插入旋转次数 | O(log n) 摊还 | 最多 2 次旋转 | 最多 2 次旋转 |
| 删除旋转次数 | O(log n) 摊还 | O(log n) 次旋转 | 最多 3 次旋转 |
| 实现复杂度 | 相对简单 | 中等 | 较复杂 |
| 空间开销 | 最小 | 略大 | 极小(1 bit) |
| 局部性利用 | 是(热点数据靠近根) | 否 | 否 |
选择建议
- 选择 Splay 树:当访问模式具有局部性(某些数据被频繁访问)时,Splay 树的性能往往优于其他平衡树。不需要存储额外信息也是其优势。
- 选择 AVL 树:查找密集型场景,需要严格的最坏情况保证时。AVL 树更严格的平衡意味着更矮的树高,查找效率最高。
- 选择红黑树:通用场景,需要稳定的插入/删除性能时。红黑树在插入和删除时旋转次数更少,是最常用的平衡搜索树。
Splay 树的实际应用
| 应用场景 | 说明 |
|---|---|
GCC STL rope |
用于高效的长字符串拼接和编辑操作 |
| Windows NT 内核 | 虚拟内存管理中的地址区间查找 |
| 缓存(Cache)实现 | 利用局部性原理自动将热点数据提升到前端 |
| 垃圾回收器(GC) | 跟踪对象引用时的快速查找 |
| 数据压缩 | 动态维护频率排序的字符表 |
| 网络路由表 | 频繁访问的路由条目被自动缓存到根部 |
Splay 树的独特价值在于它无需任何额外空间即可获得良好的摊还性能,且天然利用了数据访问的局部性特征。这使得它在那些访问模式不均匀(即某些数据被频繁访问,而其他数据很少被访问)的实际应用中表现出色。

浙公网安备 33010602011771号