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 树的几个核心特征:

  1. 每次插入后根节点等于新插入的值 — 因为新节点被 Splay 到了根部。
  2. 搜索 30 后根变为 30 — 被访问的节点自动提升到根。
  3. 删除 30 后根变为 20 — 删除后左子树最大值 20 被提升为新根。
  4. 中序遍历始终有序 — 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 树的独特价值在于它无需任何额外空间即可获得良好的摊还性能,且天然利用了数据访问的局部性特征。这使得它在那些访问模式不均匀(即某些数据被频繁访问,而其他数据很少被访问)的实际应用中表现出色。

posted @ 2026-04-16 17:48  游翔  阅读(12)  评论(0)    收藏  举报