splay的不负责胡乱接口封装 写给自己看

前言

splay很早的时候就学过了,当时也挺理解的,不过可能是因为自己太菜了把,所以很长一段时间里,我都没有使用过splay,也忘了很多,以前学习的时候写的小结也是够水的,属于那种如果比赛的时候突然要用,我肯定是用不出来的那种,所以这次重新整理了一下splay,并且把相应功能做成了接口,加上了注释,再加上一些自己的理解,做成了一个类似stl的东西(当然为了速度和编程方便,我直接把所有的成员变量和函数都直接放在了程序里,没有用class来封装,内存也不是动态申请的,而是直接开了题目范围内的最大值),希望这样能对自己和其它有需要的人有点帮助。

对splay的理解(蜜汁自信

说到set,map,很多人都明白它们的特性和功能,set是一个点的集合,内部实现是一棵红黑树,map则是在set的基础上,把集合中的元素做成了映射中的键,同时每个键有自己对应的值,成了一个键值对的集合。在这里,我(不是很慎密地)把splay定义成一个更高功能的map,它不仅支持map(在acm竞赛中)常用的操作和(排序二叉树)性质外,还多了以下功能:

  1. 名次树,可以知道某个节点(或者某个key值)在splay中的名次,也可以知道splay中排名第几的节点
  2. 区间求和,查询key值在区间[l, r]的所有节点的value的某个值(和,最值)
  3. 区间修改,对key值在区间[l, r]的所有节点的value都加上某个值
  4. 区间插入/删除,用一个区间生成splay树,插入进原有的splay,或者把key值在区间[l, r]的节点集合从splay中删去

当然了如果需要区间翻转这个功能,那我们就不能把splay看作以key值为排序关键字的数据结构(但是前面说的一大段还是很实用的!),这时我们需要用一个数组记录相应的节点编号,然后又可以完成上述的4个操作外加区间翻转。

所以某种意义上splay就是一个可分裂与合并的序列。(精分现场QAQ

使用说明

下面的代码很大程度上只是给了一个如何使用功能的示例,比赛的时候理所应当地写更容易写(没有可读性)的版本。
因为splay的均摊复杂度达到O(logn)的特性,所以可以认为splay是一棵深度为logn的树。
splay中节点携带的信息只有在把这个节点移到根节点处才是真实的(因为存在lazy[]数组),也只能修改根节点。
如果要对区间操作,那么自己定义了一个可操作区间的概念(自己define了一个op_seg),这时的区间操作和查询也是合法的,但是每次操作完后,要执行upSeg()操作,使得区间被修改的信息传递到根节点

变量说明

  • 结构类变量:pa[]son[][],指明了一个节点的父节点和子节点
  • 信息变量:key[]value[],一个节点的键值对,决定了它在splay中的位置,显然splay中key是不能改变的
  • 统计变量:siz[]sum[],这个节点代表的子树的统计信息
  • lazy变量:add[]rev[],缓存一个节点没有传递下去的操作信息

代码

#define MS(x, y) memset(x, y, sizeof(x))
#define lson(x) son[x][0]
#define rson(x) son[x][1]
#define opseg son[son[root][1]][0]

int n;
int pa[N], son[N][2], key[N], siz[N], tot, root;
LL value[N], sum[N], add[N];
bool rev[N];

// 更新一个节点的信息
inline void pushUp(int x) {
  siz[x] = siz[lson(x)] + siz[rson(x)] + 1;
  sum[x] = sum[lson(x)] + sum[rson(x)] + value[x];
}

// 把一个节点的lazy信息向下传递
inline void pushDown(int x) {
  if (add[x]) {
    add[lson(x)] += add[x]; value[lson(x)] += add[x]; sum[lson(x)] += add[x] * siz[lson(x)];
    add[rson(x)] += add[x]; value[rson(x)] += add[x]; sum[rson(x)] += add[x] * siz[rson(x)];
    add[x] = 0;
  }
  if (rev[x]) {
    rev[lson(x)] ^= true; swap(son[lson(x)][0], son[lson(x)][1]);
    rev[rson(x)] ^= true; swap(son[rson(x)][0], son[rson(x)][1]);
    rev[x] = false;
  }
}

// 每当可操作区的值有改变时,就要用一下这个函数进行更新
inline void upSeg() {
  pushUp(rson(root));
  pushUp(root);
}

// debug()的子函数
void treaval(int x) {
  if (!x) return ;
  pushDown(x);
  treaval(lson(x));
  printf("node: %2d: left: %2d right: %2d pa: %2d\n", x, lson(x), rson(x), pa[x]);
  treaval(rson(x));
}

// 打印出整棵splay树供debug用
inline void debug() {
  printf("root: %d\n", root);
  treaval(root);
  putchar(10);
}

// 生成一个key为pos,value为data,父节点为fa的节点
inline int newnode(int pos, int data, int fa) {
  int ret;
  ret = ++tot;
  pa[ret] = fa; key[ret] = pos; value[ret] = data; siz[ret] = 1, sum[ret] = data;
  add[ret] = 0; rev[ret] = false;
  MS(son[ret], 0);
  return ret;
}

// splay()的子函数
inline void Rotate(int x, int op) {
  int y = pa[x];
  pushDown(y); pushDown(x);
  son[y][!op] = son[x][op];
  pa[son[x][op]] = y;
  if (pa[y]) son[pa[y]][rson(pa[y]) == y] = x;
  pa[x] = pa[y];
  son[x][op] = y;
  pa[y] = x;
  pushUp(y);
}

// 令x为fa的子节点,若fa == 0,则令x为根节点
inline void splay(int x, int fa) {
  pushDown(x);
  int y, op;
  while (pa[x] != fa) {
    if (pa[pa[x]] == fa) Rotate(x, lson(pa[x]) == x);
    else {
      y = pa[x];
      op = (lson(pa[y]) == y);
      if (son[y][op] == x) {
        Rotate(x, !op);
        Rotate(x, op);
      } else {
        Rotate(y, op);
        Rotate(x, op);
      }
    }
  }
  pushUp(x);
  if (fa == 0) root = x;
}

// 常规的二叉树插入函数,把pos,data这个键值对插入fa的子树中
// 该函数是insert()的子函数
int bstInsert(int pos, int data, int fa, int &rt) {
  if (rt == 0) {
    rt = newnode(pos, data, fa);
    return rt;
  }
  pushDown(rt);
  // assert(pos != key[rt])
  int ret;
  if (pos < key[rt]) ret = bstInsert(pos, data, rt, lson(rt));
  else ret = bstInsert(pos, data, rt, rson(rt));
  pushUp(rt);
  return ret;
}

// 把某数组的[l, r]区间变成父节点是fa的一棵splay树
int build(int fa, int l, int r) {
  if (l > r) return 0;
  int mid = (l + r) >> 1;
  int rt = newnode(mid, fa);
  lson(rt) = build(rt, l, mid - 1);
  rson(rt) = build(rt, mid + 1, r);
  pushUp(rt);
  return rt;
}

// 返回最后一个key值小于pos的节点编号
int getPrev(int pos, int rt) {
  if (rt == 0) return 0;
  pushDown(rt);
  if (pos <= key[rt]) return getPrev(pos, lson(rt));
  int ret = getPrev(pos, rson(rt));
  if (ret) return ret;
  return rt;
}

// 返回第一个key值大于pos的节点编号
int getNext(int pos, int rt) {
  if (rt == 0) return 0;
  pushDown(rt);
  if (key[rt] <= pos) return getNext(pos, rson(rt));
  int ret = getNext(pos, lson(rt));
  if (ret) return ret;
  return rt;
}

// 找到key值恰好为pos的节点编号,没有返回-1
inline int find(int pos) {
  int rt = root;
  while (rt) {
    if (key[rt] < pos) rt = rson(rt);
    if (pos < key[rt]) rt = lson(rt);
    if (key[rt] == pos) return rt;
  }
  return -1;
}

// 删除根节点,这个是erase(x)的子函数
inline void deleteRoot() {
  pushDown(root);
  if (!lson(root) || !rson(root)) {
    root = lson(root) + rson(root);
    pa[root] = 0;
    return ;
  }
  int k = getNext(key[root], root);
  splay(k, root);
  lson(k) = lson(root);
  pa[lson(root)] = k;
  root = k;
  pa[root] = 0;
  pushUp(root);
}

// 把key值在区间[l, r]的节点集合移到可操作区
inline void getSegment(int l, int r) {
  l = getPrev(l, root);
  r = getNext(r, root);
  splay(l, 0);
  splay(r, root);
}

// 把区间[l, r]翻转,这个操作违背了二叉排序树的性质
// 所以要用到这个操作的题不用key value对,而是直接把要操作的节点编号存下来
inline void Reverse(int l, int r) {
  getSegment(l, r);
  rev[opseg] ^= true;
  swap(lson(opseg), rson(opseg));
}

// 删除key值在区间[l, r]的节点集合
inline void deleteSegment(int l, int r) {
  getSegment(l, r);
  opseg = 0;
  upSeg();
}

// 对key值在区间[l, r]的所有节点都加上v
inline void update(int l, int r, int v) {
  getSegment(l, r);
  add[opseg] += v;
  value[opseg] += v;
//  sum[opseg] += 1LL * v * siz[opseg];
  upSeg();
}

// 把key为pos,value为data的键值对加入splay中
inline void insert(int pos, int data) {
  int ret = bstInsert(pos, data, 0, root);
  splay(ret, 0);
}

// 把编号为x的节点从splay中移除
inline void erase(int x) {
  splay(x, 0);
  deleteRoot();
}

// 得到key值为pos的rank(是splay中的第几个节点)
inline int getRank(int pos) {
  int x = find(pos);
  if (x == -1) return -1;
  splay(x, 0);
  return siz[lson(x)] + 1;
}

// 得到key值排名第k大的节点编号
inline int getKth(int k) {
  int x = root, s;
  while (x) {
    pushDown(x);
    s = siz[lson(x)] + 1;
    if (s == k) return x;
    if (s > k) x = lson(x);
    else k -= s, x = rson(x);
  }
}

// 初始化
// 把节点0的所有信息都置为0
inline void init() {
  tot = 0;
  pa[0] = son[0][0] = son[0][1] = value[0] = key[0] = siz[0] = sum[0] = add[0] = rev[0] = 0;
// case # 1: splay开始为空,并且以后的操作中也会变空,那么我们加入一个key超级小的点和key超级大的点
  root = newnode(-1, 0, 0);
  rson(root) = newnode(INF, 0, root);
  upSeg();
// case # 2: splay开始是一个序列,并且以后的操作中不会变空,那么我们把这个区间建成splay就好
  root = build(0, 1, n);
}
<think>好的,我需要为用户提供一个完整的伸展树(SplayTree)的Java代码实现,包括插入、删除和查找等基本操作。首先,我需要回忆伸展树的基本原理和操作步骤。伸展树是一种自平衡二叉搜索树,通过每次访问节点后进行伸展操作,将最近访问的节点移动到根的位置,从而保持较高的访问效率。伸展操作包括旋转和调整结构,常见的伸展策略有自底向上和自顶向下两种方式,这里可能需要选择其中一种来实现。 接下来,我需要确定代码的结构。通常,伸展树的节点类需要包含值、左子节点和右子节点。然后,伸展树类包含根节点,并实现插入、删除、查找等方法。伸展操作是关键,可能涉及到单旋转、之字形(zig-zag)和一字形(zig-zig)旋转。我需要确保这些旋转操作正确无误,以保证伸展后的树结构符合预期。 对于插入操作,首先需要按照二叉搜索树的规则找到插入位置,插入新节点后,对该节点进行伸展,使其成为根节点。删除操作稍微复杂一些,需要先找到目标节点,伸展到根,然后删除根节点,并将左右子树合并。查找操作则是在查找成功后,将找到的节点伸展到根,以提高后续访问的效率。 在编写代码时,需要注意处理各种边界情况,比如空树、只有一个节点的树,以及各种旋转情况下的节点连接。同时,要确保代码的注释清晰,结构合理,便于用户理解。 另外,根据用户提供的引用内容,用户可能已经了解二叉搜索树和AVL树的基本实现,因此需要确保伸展树的代码结构与其他树结构类似,但突出其特有的伸展操作。例如,在查找方法中,找到节点后需要调用splay方法进行伸展。 现在,我需要检查是否有现成的Java实现可以参考,或者是否需要自行编写。根据我的知识库,伸展树的标准实现需要包含节点类、伸展方法以及基本操作。可能还需要辅助方法,如旋转方法(左旋、右旋)以及合并子树的方法。 最后,测试代码的正确性也很重要,但用户可能只需要代码实现,因此需要确保提供的代码逻辑正确,语法无误,并附上必要的注释说明每个步骤的作用。</think>以下是伸展树(Splay Tree)的完整Java实现,包含插入、删除和查找操作的核心逻辑: ### 伸展树(Splay Tree)实现 ```java public class SplayTree { static class Node { int key; Node left, right, parent; Node(int key) { this.key = key; left = right = parent = null; } } private Node root; // 右旋转(Zig) private void rightRotate(Node x) { Node 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.right) { x.parent.right = y; } else { x.parent.left = y; } if (y != null) { y.right = x; } x.parent = y; } // 左旋转(Zag) private void leftRotate(Node x) { Node 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; } // 伸展操作(Splay) private void splay(Node x) { while (x.parent != null) { if (x.parent.parent == null) { // 单层旋转 if (x == x.parent.left) { rightRotate(x.parent); } else { leftRotate(x.parent); } } else if (x.parent.left == x && x.parent.parent.left == x.parent) { // Zig-Zig rightRotate(x.parent.parent); rightRotate(x.parent); } else if (x.parent.right == x && x.parent.parent.right == x.parent) { // Zag-Zag leftRotate(x.parent.parent); leftRotate(x.parent); } else if (x.parent.right == x && x.parent.parent.left == x.parent) { // Zag-Zig leftRotate(x.parent); rightRotate(x.parent); } else { // Zig-Zag rightRotate(x.parent); leftRotate(x.parent); } } } // 查找操作 public Node search(int key) { Node curr = root; while (curr != null) { if (curr.key == key) { splay(curr); return curr; } else if (curr.key > key) { curr = curr.left; } else { curr = curr.right; } } return null; } // 插入操作 public void insert(int key) { Node node = new Node(key); Node curr = root, parent = null; while (curr != null) { parent = curr; if (key < curr.key) { curr = curr.left; } else { curr = curr.right; } } node.parent = parent; if (parent == null) { root = node; } else if (key < parent.key) { parent.left = node; } else { parent.right = node; } splay(node); } // 删除操作 public void delete(int key) { Node node = search(key); if (node == null) return; if (node.left == null) { root = node.right; if (root != null) root.parent = null; } else { Node rightSubtree = node.right; root = node.left; root.parent = null; Node maxLeft = findMax(root); splay(maxLeft); root.right = rightSubtree; if (rightSubtree != null) rightSubtree.parent = root; } } private Node findMax(Node node) { while (node.right != null) { node = node.right; } return node; } } ``` ### 核心操作说明 1. **伸展操作**:通过`Zig`(单右旋)和`Zag`(单左旋)的组合实现节点的提升,包括: - **Zig-Zig**:连续右旋两次 - **Zag-Zag**:连续左旋两次 - **Zig-Zag**:先右旋后左旋 - **Zag-Zig**:先左旋后右旋[^1] 2. **查找优化**:每次成功查找后,目标节点会被伸展到根节点,提升后续访问效率[^3] 3. **删除策略**:通过将目标节点伸展到根后,合并左右子树实现删除操作,保持了$O(\log n)$的摊还时间复杂度[^4]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值