1 定义
伸展树(Splay Tree)是特殊的非平衡二叉查询树,对比二叉搜索树,具备一个特点:
当某个结点被访问时,伸展树会通过旋转使该结点成为树根。这样做的好处是,下次要访问该结点时,能够迅速的访问到该结点。
这也是基于统计学里面的一个现象,当某个结点被访问后,其有较大的概率在短时间内会再次被访问到。虽然伸展树是非平衡的
二叉查询树,但任何情况下其平均操作复杂度是\large o(\log_{2}N)。
2 特性
普通的二叉查找树相比,具有任何情况下、任何操作的平摊复杂度为\large o(\log_{2}N)
和一般的平衡二叉树比如 红黑树、AVL树相比,其维护更少的节点额外信息,空间性能更优,同时编程复杂度更低
在很多情况下,对于查找操作,后面的查询和之前的查询有很大的相关性。这样每次查询操作将被查到的节点旋转到树的根节点位置,这样下次查询操作可以很快的完成
可以完成对区间的查询、修改、删除等操作,可以实现线段树和树状数组的所有功能
3 旋转
3.1 旋转的条件
(1)对于AVL树,当检查到某个结点不平衡时(左右子树高度差的绝对值大于等于2),就需要对该结点做rotation操作,
但是伸展树是检查某个节点不为空的时候旋转
(2)对于AVL树,旋转的是当前节点,但是伸展树旋转的是父节点或者祖父节点
3.2 旋转的6种情况
对比AVL,多了4种情况
(1)L:Right
当前节点没有祖父,只有父亲,并且是父亲的左子,将父亲右旋
(2)R:Left
当前节点没有祖父,只有父亲,并且是父亲的右子,将父亲左旋
(3)LL:Right-Right
当前节点有祖父,有父亲,并且是父亲的左子,父亲是祖父的左子,
先将父亲右旋,再将祖父右旋
(4)RR:Left-Left
当前节点有祖父,有父亲,并且是父亲的右子,父亲是祖父的右子,
先将父亲左旋,再将祖父左旋
(5)RL:Right-Left
当前节点有祖父,有父亲,并且是父亲的左子,父亲是祖父的右子,
先将父亲右旋,再将祖父左旋
(6)LR:Left-Right
当前节点有祖父,有父亲,并且是父亲的右子,父亲是祖父的左子,
先将父亲左旋,再将祖父右旋
4 代码实现
基于BinarySearchTree
package com.my.study.algorithm.tree.splaytree;
import com.my.study.algorithm.tree.bstree.BinarySearchTree;
import com.my.study.algorithm.tree.bstree.IBinaryTreeNode;
public class SplayTree<E extends Comparable<E>> extends BinarySearchTree<E> {
public SplayTree() {
}
public SplayTree(E[] data) {
super(data);
}
/**
*
* 插入
* param:e
*
*/
@Override
public IBinaryTreeNode<E> insert(E e) {
IBinaryTreeNode<E> newNode = super.insert(e);
splay(newNode);
return newNode;
}
/**
*
* 删除
* param:e
*
*/
@Override
public IBinaryTreeNode<E> remove(E e) {
IBinaryTreeNode<E> node = super.find(e);
if (node == null) {
return null;
} else {
splay(node);
return super.remove(e);
}
}
/**
*
* 查询
* param:e
*
*/
@Override
public IBinaryTreeNode<E> find(E e) {
IBinaryTreeNode<E> node = super.find(e);
if (node == null) {
return null;
} else {
splay(node);
}
return node;
}
/**
*
* 伸展:6种旋转情况
* param:node
*
*/
private void splay(IBinaryTreeNode<E> node) {
//父亲不为空
while (node.getParentNode() != null) {
// 祖父为空
if (node.getParentNode().getParentNode() == null) {
//当前节点是左子
if (node.isLeft()) {
rotateRight(node.getParentNode());
}
当前节点是右子
else if (node.isRight())
{
rotateLeft(node.getParentNode());
}
}
// 祖父不为空
else
{
//当前节点是左子
if (node.isLeft()) {
//父亲节点是左子
if (node.getParentNode().isLeft()) {
rotateRightRight(node.getParentNode().getParentNode());
}
//父亲节点是右子
else if (node.getParentNode().isRight())
{
rotateRightThenLeft(node.getParentNode().getParentNode());
}
}
//当前节点是右子
else if (node.isRight())
{
//父亲节点是左子
if (node.getParentNode().isLeft()) {
rotateLeftThenRight(node.getParentNode().getParentNode());
}
//父亲节点是右子
else if (node.getParentNode().isRight())
{
rotateLeftLeft(node.getParentNode().getParentNode());
}
}
}
}
// 当前节点设置为根
root = node;
}
/**
* 左旋
*/
private IBinaryTreeNode<E> rotateLeft(IBinaryTreeNode<E> nodeA) {
IBinaryTreeNode<E> nodeB = nodeA.getRightNode();
nodeB.setParentNode(nodeA.getParentNode());
nodeA.setRightNode(nodeB.getLeftNode());
if (nodeB.getParentNode() != null) {
if (nodeA.isLeft()) {
nodeB.getParentNode().setLeftNode(nodeB);
} else {
nodeB.getParentNode().setRightNode(nodeB);
}
}
nodeB.setLeftNode(nodeA);
return nodeB;
}
/*
*右旋
*/
private IBinaryTreeNode<E> rotateRight(IBinaryTreeNode<E> nodeA) {
IBinaryTreeNode<E> nodeB = nodeA.getLeftNode();
nodeB.setParentNode(nodeA.getParentNode());
nodeA.setLeftNode(nodeB.getRightNode());
if (nodeB.getParentNode() != null) {
if (nodeA.isLeft()) {
nodeB.getParentNode().setLeftNode(nodeB);
} else {
nodeB.getParentNode().setRightNode(nodeB);
}
}
nodeB.setRightNode(nodeA);
return nodeB;
}
/*
* 左旋再右旋
*/
private IBinaryTreeNode<E> rotateLeftThenRight(IBinaryTreeNode<E> node) {
node.setLeftNode(rotateLeft(node.getLeftNode()));
return rotateRight(node);
}
/*
* 右旋再左旋
*/
private IBinaryTreeNode<E> rotateRightThenLeft(IBinaryTreeNode<E> node) {
node.setRightNode(rotateRight(node.getRightNode()));
return rotateLeft(node);
}
/*
* 左旋再左旋
*/
private IBinaryTreeNode<E> rotateLeftLeft(IBinaryTreeNode<E> node) {
return rotateLeft(rotateLeft(node));
}
/*
* 右旋再右旋
*/
private IBinaryTreeNode<E> rotateRightRight(IBinaryTreeNode<E> node) {
return rotateRight(rotateRight(node));
}
}