SPLAY树

前言

在讲SPLAY树之前,我们先复习一下几个东西

二叉搜索树(BST)

二叉搜索树是一颗满足如下性质的二叉树:左子树值<=根节点值<=右子树值。因此理论上我们可以在 O(logN)的时间内完成插入、删除、查找等操作。是一种在“动态维护”中效果相当不错的数据结构。但由于题目数据的原因,可能造成二叉查找树并不平衡(严重的时候可能退化为线性),致使插入、删除、查找等操作的复杂度退化为O(N)。为了尽量保持二叉搜索树的平衡,我们需要去维护二叉搜索树——平衡二叉树。

平衡二叉树之旋转

首先介绍所有平衡二叉树都需要进行的操作——旋转(Rotate)。旋转包括左旋 (Zag) 和右旋 (Zig) 。下面我们以右旋为例来分析旋转操作,我们想把 X 通过右旋旋转到目前 Y 的位置。我们知道 Y 的左子树中所有节点权值都小于等于 Y,于是我们完全可以让 X 的右子树去充当 Y的左子树, 由于 Y 节点权值大于 X , 我们又可让 Y 来充当 X 的右子树,通过这样的旋转操作, X 便到了目前Y 的位置。

平衡二叉树的左旋和右旋

如图
这里写图片描述
大概的左旋右旋粗略来讲就是这样

可以记成:我是你的孩子,我现在有了孩子,我把我的孩子送给你当孩子,最后我来当你的爸爸。

正题

现在让我们看看SPLAY树是什么

定义

   伸展树(Splay Tree),也叫分裂树,是一种二叉排序树,它能在O(log n)内完成插入、查找和删除操作。它由Daniel Sleator和Robert Tarjan创造,后勃刚对其进行了改进。它的优势在于不需要记录用于平衡树的冗余信息。在伸展树上的一般操作都基于伸展操作。

旋转操作

大致的旋转操作分为四种

   情况一:节点x的父节点y是根节点。这时,如果xy
的左孩子,我们进行一次Zig(右旋)操作;如果xy
的右孩子,则我们进行一次Zag(左旋)操作。经过旋
转,x成为二叉查找树S的根节点,调整结束。即:如果
当前结点父结点即为根结点,那么我们只需要进行一次简
单旋转即可完成任务,我们称这种旋转为单旋转。
(注:这里我把父节点都初始化为-1了)
  int y=tree[x].father;
  int z=tree[y].father;
  if(z==-1){
    if(tree[y].l==x)
        right_rorate(x);
    else 
        left_rorate(x);
        }
   情况二:节点x 的父节点y 不是根节点,y 的父节点为
z,且xy 同时是各自父节点的左孩子或者同时是各自
父节点的右孩子。这时,我们进行一次Zig-Zig操作或者
Zag-Zag操作。即:设当前结点为XX 的父结点为
YY 的父结点为Z ,如果YX 同为其父亲的左孩
子或右孩子,那么我们先旋转Y ,再旋转X 。我们称这
种旋转为一字形旋转。
    if(tree[z].l==y&&tree[y].l==x){
       right_rorate(y);
       right_rorate(x);     
    }
     if(tree[z].r==y&&tree[y].r==x){
           left_rorate(y);
       left_rorate(x);  
    }
   情况三:节点x的父节点y不是根节点,y的父节点为
zxy中一个是其父节点的左孩子而另一个是其父节
点的右孩子。这时,我们进行一次Zig-Zag操作或者Zag-
Zig 操作。即:这时我们连续旋转两次X 。我们称这种旋
转为之字形旋转。
    if(tree[z].l==y&&tree[y].r==x){
      left_rorate(x);
      right_rorate(x);  
     }
     if(tree[z].r==y&&tree[y].l==x){
       right_rorate(x);
       left_rorate(x);  
      }
(这里为什么会连续旋转两次是因为第一次以后y不再是x的父节点了,所以等于是现在的x才是之前x的父节点,所以我们旋转x)

查找、插入

    查找操作即为普通 BST 的查找操作,如果待查找值等于当前节点值便返回当前节点编号,小于根节点值便在左子树找,否则便在右子树查找。
    插入操作首先通过查找操作确定当前需要插入点的位置,然后判断插入其左子树或者右子树,最后不要忘记对插入点进行伸展操作。

删除

   Splay 的删除操作不同于一般 BST 的删除操作,它首先将待删除点旋转到根节点,然后合并他的左右子树(即找到左子树的最大值当新树的根,将右子树现有的根与其相连)8、 前驱、后继操作——即为求第一个毕某一节点值小、大的元素首先找到该节点,并旋转到跟,前驱即为其左子树的最大值、后继为其右子树最小值9 、 求第 K 大(小)值以求第 K 小值为例,进行解释:如果 K=左子树所有节点数+1,那么当前根节点即为所求,如果 K<左子树所有节点数,那么就在左子树中查找第 K 小值,否则就在右子树中查找第(K-左子树节点数-1)小值。

区间

   在实际应用中,伸展树的中序遍历即为我们维护的数列,这就引出一个问题,怎么在伸展树中表示某个区间?比如我们要提取区间[a,b],那么我们将a前面一个数对应的结点转到树根,将b 后面一个结点对应的结点转到树根的右边,那么根右边的左子树就对应了区间[a,b]。原因很简单,将a 前面一个数对应的结点转到树根后, a 及a 后面的数就在根的右子树上,然后又将b后面一个结点对应的结点转到树根的右边,那么[a,b]这个区间就是下图中B所示的子树。

相关链接

 1.文艺平衡树(Splay) https://www.luogu.org/problem/show?pid=3391
 2.http://blog.csdn.net/changtao381/article/details/8936765
 3.DEMO:http://www.link.cs.cmu.edu/splay/

例题链接

 营业额计划 https://www.luogu.org/problem/show?pid=2234

这里写图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值