本文将主要讲述 BBST 家族的另外一种相对奇特的树,伸展树;伸展树的相较于其余的 BBST,结构更加简单,由于伸展树不须要平衡因子、颜色等信息,他的节点就是 BST 的节点,同时他甚至没有时刻维护全树的平衡状态,却仍然能保持各项操做达到分摊 O(logn)
;html
伸展树的结构和二叉树彻底相同,只是在实现上多了一步伸展;伸展树蕴含的主要思想就是数据访问的局部性,也就是说java
这一现象在咱们生活正十分的常见,好比你的电脑,可能有几百G的资料,可是常常用的可能只有百分之一;因此伸展树的核心方法就是将刚刚操做过的节点移动到根位置,如图所示:node
根据以上的描述你可能很快会想到,要想把底部的元素伸展至树根位置,只须要依次旋转其父节点便可;这样左的确能够,可是在极端状况下却可能会使时间复杂度上升至 O(n)
;如图所示:算法
图中展现了二叉树的极端状况,即退化为了列表,而后依次访问末端元素,至全部元素都访问一遍后,发现又回到了初始状态,因此这种单层的伸展是万万不可取的;ide
而双层伸展则是根据其父亲节点和祖父节点的相对位置,进行相应的旋转。并分红如下分三类状况:3d
如图所示,当祖孙三代左倾或者右倾时,先旋转祖父节点再旋转父节点;code
如图所示,当祖孙三代左倾、右倾交替时,先旋转父节点,使其转化为同为左倾或右倾,再旋转祖父节点;htm
当节点的深度为奇数时,则最后一次旋转仅为单层便可;blog
protected void splay(Node<T> node) { // move node up until its root while (node != root) { // Zig step Node<T> parent = node.parent; if (parent.equals(root)) { if (node.equals(parent.left)) { rotateRight(parent); } else if (node.equals(parent.right)) { rotateLeft(parent); } break; } else { Node<T> grandParent = parent.parent; boolean isLL = node.equals(parent.left) && parent.equals(grandParent.left); boolean isRR = node.equals(parent.right) && parent.equals(grandParent.right); boolean isRL = node.equals(parent.right) && parent.equals(grandParent.left); boolean isLR = node.equals(parent.left) && parent.equals(grandParent.right); // Zig zig step to the right if (isLL) { rotateRight(grandParent); rotateRight(parent); } // Zig zig step to the left else if (isRR) { rotateLeft(grandParent); rotateLeft(parent); } // Zig zag steps else if (isRL) { rotateLeft(parent); rotateRight(grandParent); } else if (isLR) { rotateRight(parent); rotateLeft(grandParent); } } } }
注意:这里仍然可使用以前在 AVL 树 中讲过的 3+4重构;element
如图所示,查找成功的时候只须要将目标节点伸展到树根位置;
如图所示,查找失败的时候则须要将失败的前一个节点(也就是最接近目标的节点),伸展至树根位置;
@Override public Node<T> search(T key) { if (key == null) return null; Node<T> u = super.binSearch(root, key); splay(u); return (key.compareTo(u.key) == 0) ? u : null; } // 查找最接近key的节点 public Node<T> binSearch(Node<T> v, T key) { Node<T> u = v; while (true) { int comp = key.compareTo(u.getKey()); if (comp < 0) if (u.left != null) u = u.left; else return u; // 失败于左节点 else if (comp > 0) if (u.right != null) u = u.right; else return u; // 失败于右节点 else return u; // 查找成功 } }
如图所示,插入也是同理,只需将最后插入的节点伸展至树根位置便可;
实现
@Override public Node insert(int element) { Node insertNode = super.insert(element); splay(insertNode); return insertNode; }
如图,通过一次查找后,目标节点已经移动至树根位置,若此时树根节点的左孩子或者右孩子为空,则能够直接删除,而后令其后代代替;
如图,当根节点同时拥有两个孩子的时候:
public Node<T> delete2(T key) { Node<T> node = search(key); if (key.compareTo(node.key) != 0) { return node; } // 查找成功,此时目标节点必然在树根处 if (root.left == null) { root = root.right; if (root != null) { root.parent = null; } } else if (root.right == null) { root = root.left; if (root != null) { root.parent = null; } } else { Node<T> t1 = root.left; Node<T> t2 = root.right; t1.parent = null; t2.parent = null; root.left = null; root.right = null; root = t1; // 查找必然失败,可是左子树中最大的节点已经伸展至树根位置,且右子树必然为空;(无相同节点) search(key); root.right = t2; t2.parent = root; } return node; }
同时这里也能够简单实现,即便用二叉树的删除,最后在伸展一次:
@Override public Node<T> delete(T key) { Node<T> deleteNode = super.delete(key); splay(deleteNode); return deleteNode; }
无需记录高度等信息,相对 AVL 树的实现而言,更简单一点,同时伸展树的各项操做均为 分摊 O(logn)
;
不能杜绝单次最坏状况,因此不能用于效率敏感的场合;