伸展树(Splay)

伸展树(Splay)

Splay 是一种二叉查找树,它通过不断将某个节点旋转到根节点,使得整棵树仍然满足二叉查找树的性质,并且保持平衡而不至于退化为链。它由 Daniel Sleator 和 Robert Tarjan 发明。

旋转

旋转(Rotate)的最终目的是将该节点的深度 − 1 -1 1并且保证二叉搜索树的结构不改变。

根据节点 x x x是父节点的左子节点或者是右子节点分为右旋和左旋转。

我们以右旋为例,我们发现,如果我们想让节点 X X X的深度 − 1 -1 1,我们必须让 X X X Z Z Z的子节点,至于是左右子节点,取决于 Y Y Y的位置,因为 X X X是代替 Y Y Y的位置。那么 b b b就可以做 Y Y Y的左节点,进而 Y Y Y X X X的右节点。

上述过程就是节点右旋的过程,我们将 X X X的深度 − 1 -1 1而不改变二叉搜索树的性质。

而左旋正好和右旋对称,读者可以自己尝试推导一下。

这两种情况对应的选择操作也叫做Zig。

右旋

我们可以将这两个操作结合成一个函数,让这个函数自己判断是左旋还是右旋。


void rotate(int x)
{
    // 获取父节点和祖父节点
    int y = tree[x].fa;
    int z = tree[y].fa;

    // 连接祖父节点和X
    if (tree[z].l == y)
        tree[z].l = x;
    else
        tree[z].r = x;
    tree[x].fa = z;

    // 连接父节点和X的子节点,连接父节点和X
    if (tree[y].l == x)
    {
        tree[y].l = tree[x].r;
        tree[tree[x].r].fa = y;

        tree[x].r = y;
        tree[y].fa = x;
    }
    else
    {
        tree[y].r = tree[x].l;
        tree[tree[x].l].fa = y;

        tree[x].l = y;
        tree[y].fa = x;
    }
}

伸展

伸展(Splay)是伸展树的核心操作,其最终目的是将节点 X X X一直向上选择直到作为节点 R R R的子节点为止或者将 X X X一直旋转到树的根节点为止。

除了完成最终目的,我们还要尽可能的降低BST的深度。

伸展分为三种操作,Zig,Zig-Zig和Zig-Zag。

  • 如果 R R R就是 X X X的祖父节点,那么我们直接一次旋转 X X X即可达成目的,这种情况叫做Zig。
  • 如果 X Y Z XYZ XYZ三者共线,那么我们先旋转 Y Y Y,再旋转 X X X。这种情况叫做ZIg-Zag。是双旋操作。
  • 如果 X Y Z XYZ XYZ三者不共线,那么我们先旋转 X X X,再旋转 X X X。这种情况叫做ZIg-Zig。也是是双旋操作。

为什么要三者共线的时候要先旋转 Y Y Y再旋转 X X X,这种双旋转结构可以降低树的层数。在 H > 4 H>4 H>4的时候才能看出来效果。具体参考:

Splay的效率讨论

那我们就可以写出代码。

void splay(int x, int r)
{
    int y;
    while ((y = tree[x].fa) != r) // 如果y不是目标节点
    {
        int z = tree[y].fa; // 获取祖父节点
        if (z != r) // 如果z是不是目标节点
        {
            // 执行双旋转
            if (tree[z].l == y && tree[y].l == x || tree[z].r == y && tree[y].r == x)
                rotate(y);
            else
                rotate(x);
            rotate(x);
        }
        else
            rotate(x); // 直接单旋即可
    }

    if (r == 0)
        root = x;
}

其他操作

其他查询、插入、删除等操作和二叉搜索树完全一直,只不过在找到目标节点之后,要将该节点Splay一下。

Splay与其他二叉搜索树不同的是Splay操作,可以把任意一个节点接到其子节点上,通常用两边夹的方法筛选节点。

P3391 文艺平衡树

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值