平衡二叉树之伸展树(Splay Tree)原理

        伸展树(Splay Tree)是一种高效的自适应二叉搜索树,它通过“伸展”操作将最近访问的节点移动到根位置,从而在摊还时间复杂度下保证操作的效率。


目录

一、伸展树的核心思想

1. 什么是伸展树?

2. 伸展操作(Splay)

3. 时间复杂度分析

二、伸展树的Java实现

1. 节点结构

2. 旋转操作

(1) 左旋(Left Rotation)

(2) 右旋(Right Rotation)

3. 伸展操作(Splay)

4. 插入操作

5. 查找操作

6. 删除操作

三、应用场景

四、总结


一、伸展树的核心思想

1. 什么是伸展树?

伸展树是一种自平衡二叉搜索树,其核心特点是:

  • 自适应性:每次访问节点(查找、插入、删除)后,通过“伸展(Splay)”操作将该节点移动到根位置。

  • 无需额外存储平衡信息:不依赖AVL树的平衡因子或红黑树的颜色标记,仅通过旋转操作调整结构。

  • 局部性原理:频繁访问的节点会被移动到靠近根的位置,减少后续访问时间。

2. 伸展操作(Splay)

伸展操作通过以下三种旋转组合将节点提升到根:

  • Zig(单旋转):当目标节点是根的左子节点或右子节点时使用。

  • Zig-Zig(同方向双旋转):当目标节点和父节点同为左子或右子时使用。

  • Zig-Zag(异方向双旋转):当目标节点和父节点方向相反时使用。

3. 时间复杂度分析

  • 摊还时间复杂度:每个操作的摊还成本为 O(log n)

  • 最坏情况:单次操作可能达到 O(n),但长期操作效率趋于高效。


二、伸展树的Java实现

1. 节点结构

每个节点保存值、父节点、左右子节点信息。

class SplayTreeNode {
    int key;
    SplayTreeNode left;
    SplayTreeNode right;
    SplayTreeNode parent;

    public SplayTreeNode(int key) {
        this.key = key;
        this.left = null;
        this.right = null;
        this.parent = null;
    }
}

2. 旋转操作

(1) 左旋(Left Rotation)
private void leftRotate(SplayTreeNode x) {
    SplayTreeNode y = x.right;
    if (y != null) {
        x.right = y.left;
        if (y.left != null) {
            y.left.parent = x;
        }
        y.parent = x.parent;
    }
    if (x.parent == null) {
        root = y;
    } else if (x == x.parent.left) {
        x.parent.left = y;
    } else {
        x.parent.right = y;
    }
    if (y != null) {
        y.left = x;
    }
    x.parent = y;
}
(2) 右旋(Right Rotation)
private void rightRotate(SplayTreeNode x) {
    SplayTreeNode y = x.left;
    if (y != null) {
        x.left = y.right;
        if (y.right != null) {
            y.right.parent = x;
        }
        y.parent = x.parent;
    }
    if (x.parent == null) {
        root = y;
    } else if (x == x.parent.left) {
        x.parent.left = y;
    } else {
        x.parent.right = y;
    }
    if (y != null) {
        y.right = x;
    }
    x.parent = y;
}

3. 伸展操作(Splay)

将目标节点移动到根位置。

private void splay(SplayTreeNode node) {
    while (node.parent != null) {
        SplayTreeNode parent = node.parent;
        SplayTreeNode grandParent = parent.parent;

        // Case 1: Zig
        if (grandParent == null) {
            if (node == parent.left) {
                rightRotate(parent);
            } else {
                leftRotate(parent);
            }
        } else {
            // Case 2: Zig-Zig
            if (parent.left == node && grandParent.left == parent) {
                rightRotate(grandParent);
                rightRotate(parent);
            } else if (parent.right == node && grandParent.right == parent) {
                leftRotate(grandParent);
                leftRotate(parent);
            } 
            // Case 3: Zig-Zag
            else if (parent.right == node && grandParent.left == parent) {
                leftRotate(parent);
                rightRotate(grandParent);
            } else {
                rightRotate(parent);
                leftRotate(grandParent);
            }
        }
    }
    root = node;
}

4. 插入操作

插入节点后执行伸展。

public void insert(int key) {
    SplayTreeNode node = new SplayTreeNode(key);
    SplayTreeNode current = root;
    SplayTreeNode parent = null;

    while (current != null) {
        parent = current;
        if (key < current.key) {
            current = current.left;
        } else {
            current = current.right;
        }
    }

    node.parent = parent;
    if (parent == null) {
        root = node;
    } else if (key < parent.key) {
        parent.left = node;
    } else {
        parent.right = node;
    }

    splay(node); // 插入后伸展
}

5. 查找操作

查找后伸展目标节点。

public SplayTreeNode search(int key) {
    SplayTreeNode node = root;
    while (node != null) {
        if (key < node.key) {
            node = node.left;
        } else if (key > node.key) {
            node = node.right;
        } else {
            splay(node); // 找到后伸展
            return node;
        }
    }
    return null;
}

6. 删除操作

删除节点并合并子树。

public void delete(int key) {
    SplayTreeNode node = search(key);
    if (node == null) return;

    splay(node); // 确保删除的节点是根

    if (node.left == null) {
        root = node.right;
        if (root != null) {
            root.parent = null;
        }
    } else if (node.right == null) {
        root = node.left;
        root.parent = null;
    } else {
        SplayTreeNode leftSubtree = node.left;
        leftSubtree.parent = null;
        root = node.right;
        root.parent = null;

        // 找到右子树的最小节点并伸展
        SplayTreeNode minNode = root;
        while (minNode.left != null) {
            minNode = minNode.left;
        }
        splay(minNode);
        minNode.left = leftSubtree;
        leftSubtree.parent = minNode;
    }
}
 

三、应用场景

  • 缓存系统:频繁访问的数据被移动到根附近,提高访问速度。

  • 网络路由表:动态调整路由路径的访问优先级。

  • 实时数据处理:快速响应最近更新的数据。


四、总结

优点

  • 实现简单,无需额外平衡信息。

  • 适合局部性强的访问场景。

缺点

  • 单次操作可能较慢(但摊仍高效)。

  • 严格实时系统需谨慎使用。

        伸展树通过动态调整结构,在多数场景下提供高效操作。本文的Java实现展示了其核心逻辑,读者可根据需求扩展更多功能(如范围查询)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值