数据结构 - 伸展树的Java实现
阅读本节前请先阅读上一篇文章:https://blog.csdn.net/funnyrand/article/details/81745518,该文章讲述了AVL树的原理和Java实现。需要理解AVL树的4种旋转方式及原理。
一、简介
伸展树(Splay Tree)是特殊的非平衡二叉查询树,它的特殊性是指,它除了本身是棵二叉查询树之外,它还具备一个特点: 当某个结点被访问时,伸展树会通过旋转使该结点成为树根。这样做的好处是,下次要访问该结点时,能够迅速的访问到该结点。这也是基于统计学里面的一个现象,当某个结点被访问后,其有较大的概率在短时间内会再次被访问到。虽然伸展树是非平衡的二叉查询树,但任何情况下其平均操作复杂度是。
参考以下链接可模拟伸展树的操作:https://www.cs.usfca.edu/~galles/visualization/SplayTree.html
二、特性
- 和普通的二叉查找树相比,具有任何情况下、任何操作的平摊复杂度为
- 和一般的平衡二叉树比如 红黑树、AVL树相比,其维护更少的节点额外信息,空间性能更优,同时编程复杂度更低
- 在很多情况下,对于查找操作,后面的查询和之前的查询有很大的相关性。这样每次查询操作将被查到的节点旋转到树的根节点位置,这样下次查询操作可以很快的完成
- 可以完成对区间的查询、修改、删除等操作,可以实现线段树和树状数组的所有功能
三、旋转
伸展树共有六种旋转(rotation)方式, right, left, right-left, left-right, left-left, right-right。其中,right, left, right-left, left-right和AVL树的旋转类似, left-left, and right-right 是Splay树特有的。
为什么AVL树有四种rotation,而Splay树有六种呢?相对于AVL树,Splay树增加了right-right和left-left两种rotation,两层rotation的作用是使任何情况下平均操作复杂度为,否则如果只采用一层rotation(left,right),在特殊情况下,平均操作复杂度会变为。
另外,AVL树和Splay树有着不同的选择rotaion的方式。对AVL树,当检查到某个结点不平衡时(左右子树高度差的绝对值大于等于2),就需要对该结点做rotation操作。以该结点为起点,检查其左子树或右子树是什么类型(LL,RR,LR,RL),再选择对应的rotation方式(LL->Right,RR->Left,LR->Left-Right,RL->Right-Left)。对Splay树,如果需要对某个结点做Splay操作,则需要对该结点的父结点或祖父结点做rotation操作,以该结点为起点,检查其父结点(如果存在)或祖父结点(如果存在)的状态。如果父结点为null,则当前结点就是root结点,不需要rotation。如果父结点不为null,而祖父结点为null,则只需要对父结点做Right或Left rotation。如果祖父结点不为null,则检查当前结点,父结点和祖父结点的关系类型(LL,RR,LR,RL),再选择对应的rotation方式(LL->Right,RR->Left,LR->Left-Right,RL->Right-Left)操作祖父节点。
注意:AVL树rotation的结点是当前结点,而Splay树rotation的结点是当前结点的父节点或祖父结点。
Right:
splay(3),此时结点3只有父结点,没有祖父结点,且3是其父结点的左结点,所以只需要对3的父结点5做一次right rotation,即可将结点3旋转至root结点。
Left:
splay(5),同理,结点5只有父结点,没有祖父结点,且5是其父结点的右子树,所以只需要对5的父结点3做一次left rotation,即可将结点5旋转至root结点。
Right-Right:
splay(2),结点2有父结点和祖父结点,且他们的关系类型是LL(Left and Left),所以需要先对2的父结点3做right rotation,再对其祖父结点5做right rotation。
Left-Left:
splay(6),结点6有父结点和祖父结点,且他们的关系类型是RR(Right and Right),所以需要先对结点6的父结点5做left rotation,再对其祖父结点3做left rotation。
Right-Left:
splay(4),结点4有父结点和祖父结点,且他们的关系类型是RL(Right and Left),所以需要先对结点4的父结点5做right rotation,再对其祖父结点3做left rotation。
Left-Right:
splay(4),结点4有父结点和祖父结点,且他们的关系类型是LR(Left and Right),所以需要先对结点4的父结点3做left rotation,再对其祖父结点5做right rotation。
四、Java实现
由于伸展树继承自BinarySearchTree,所以下面的代码都是基于这篇文章中的代码:https://blog.csdn.net/funnyrand/article/details/81665445
实现类SplayTree:
package com.my.study.algorithm.tree.splaytree;
import com.my.study.algorithm.tree.bstree.BinarySearchTree;
import com.my.study.algorithm.tree.bstree.IBinaryTreeNode;
/**
* This class demonstrates the usage of Splay tree.
*
* @param <E>
* Element type.
*/
public class SplayTree<E extends Comparable<E>> extends BinarySearchTree<E> {
public SplayTree() {
}
public SplayTree(E[] data) {
super(data);
}
@Override
public IBinaryTreeNode<E> insert(E e) {
IBinaryTreeNode<E> newNode = super.insert(e);
splay(newNode);
return newNode;
}
@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);
}
}
@Override
public IBinaryTreeNode<E> find(E e) {
IBinaryTreeNode<E> node = super.find(e);
if (node == null) {
return null;
} else {
splay(node);
}
return node;
}
/*
* Splay node, there are 6 rotations, left, right, left-left, left-right,
* right-right, right-left.
*
* @param node IBinaryTreeNode
*/
private void splay(IBinaryTreeNode<E> node) {
while (node.getParentNode() != null) {
// Node's parent is root
if (node.getParentNode().getParentNode() == null) {
if (node.isLeft()) {
rotateRight(node.getParentNode());
} else if (node.isRight()) {
rotateLeft(node.getParentNode());
}
// Node's parent is not root
} 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());
}
}
}
}
// After rotation, root is current node.
root = node;
}
/*
* Left rotation.
*/
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;
}
/*
* Right rotation.
*/
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;
}
/*
* Left rotation then right rotation.
*/
private IBinaryTreeNode<E> rotateLeftThenRight(IBinaryTreeNode<E> node) {
node.setLeftNode(rotateLeft(node.getLeftNode()));
return rotateRight(node);
}
/*
* Right rotation then left rotation.
*/
private IBinaryTreeNode<E> rotateRightThenLeft(IBinaryTreeNode<E> node) {
node.setRightNode(rotateRight(node.getRightNode()));
return rotateLeft(node);
}
/*
* Left left rotation.
*/
private IBinaryTreeNode<E> rotateLeftLeft(IBinaryTreeNode<E> node) {
return rotateLeft(rotateLeft(node));
}
/*
* Right right rotation.
*/
private IBinaryTreeNode<E> rotateRightRight(IBinaryTreeNode<E> node) {
return rotateRight(rotateRight(node));
}
}
测试类:
package com.my.study.algorithm.tree.splaytree;
import com.my.study.algorithm.tree.bstree.BinaryTreePrinter;
import com.my.study.algorithm.tree.bstree.IBinaryTree;
public class SplayTreeTest {
public static void main(String[] args) {
testGenerateSplayTreeByOrderedArrayInserting();
// testFindElements();
// testRemoveElement();
}
public static void testGenerateSplayTreeByOrderedArrayInserting() {
IBinaryTree<Integer> tree = new SplayTree<>();
Integer[] array1 = { 10, 5, 7, 22, 9, 0, 2, 6, 20 };
for (int data : array1) {
tree.insert(data);
System.out.println("Inserted: " + data);
printTreeInfo(tree);
}
}
public static void testFindElements() {
Integer[] array1 = { 10, 5, 7, 22, 9, 0, 2, 6, 20 };
IBinaryTree<Integer> tree = new SplayTree<>(array1);
printTreeInfo(tree);
for (int data : array1) {
tree.find(data);
System.out.println("Find: " + data);
printTreeInfo(tree);
}
}
public static void testRemoveElement() {
Integer[] array1 = { 10, 5, 7, 22, 9, 0, 2, 6, 20 };
IBinaryTree<Integer> tree = new SplayTree<>(array1);
printTreeInfo(tree);
for (int data : array1) {
tree.remove(data);
System.out.println("Removed: " + data);
printTreeInfo(tree);
}
}
private static <E extends Comparable<E>> void printTreeInfo(IBinaryTree<E> tree) {
System.out.println("Tree height: " + (tree.getRoot() == null ? 0 : tree.getRoot().getHeight()));
System.out.println("Tree size: " + tree.getSize());
System.out.println("Pre order traversal: " + tree.preorderTraversal());
System.out.println("In order traversal: " + tree.inorderTraversal());
System.out.println("Post order traversal: " + tree.postorderTraversal());
System.out.println("Tree structure:");
BinaryTreePrinter.printTree(tree);
System.out.println("-----------------------------------------------");
}
}
五、伸展树的测试
1. testGenerateSplayTreeByOrderedArrayInserting
Inserted: 10
Tree height: 1
Tree size: 1
Pre order traversal: [10]
In order traversal: [10]
Post order traversal: [10]
Tree structure:
10
-----------------------------------------------
Inserted: 5
Tree height: 1
Tree size: 2
Pre order traversal: [5, 10]
In order traversal: [5, 10]
Post order traversal: [10, 5]
Tree structure:
5
\
10
-----------------------------------------------
Inserted: 7
Tree height: 1
Tree size: 3
Pre order traversal: [7, 5, 10]
In order traversal: [5, 7, 10]
Post order traversal: [5, 10, 7]
Tree structure:
7
/ \
5 10
-----------------------------------------------
Inserted: 22
Tree height: 1
Tree size: 4
Pre order traversal: [22, 10, 7, 5]
In order traversal: [5, 7, 10, 22]
Post order traversal: [5, 7, 10, 22]
Tree structure:
22
/
/
/
/
10
/
/
7
/
5
-----------------------------------------------
Inserted: 9
Tree height: 1
Tree size: 5
Pre order traversal: [9, 7, 5, 22, 10]
In order traversal: [5, 7, 9, 10, 22]
Post order traversal: [5, 7, 10, 22, 9]
Tree structure:
9
/ \
/ \
7 22
/ /
5 10
-----------------------------------------------
Inserted: 0
Tree height: 1
Tree size: 6
Pre order traversal: [0, 9, 5, 7, 22, 10]
In order traversal: [0, 5, 7, 9, 10, 22]
Post order traversal: [7, 5, 10, 22, 9, 0]
Tree structure:
0
\
\
\
\
9
/ \
/ \
5 22
\ /
7 10
-----------------------------------------------
Inserted: 2
Tree height: 1
Tree size: 7
Pre order traversal: [2, 0, 5, 9, 7, 22, 10]
In order traversal: [0, 2, 5, 7, 9, 10, 22]
Post order traversal: [0, 7, 10, 22, 9, 5, 2]
Tree structure:
2
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
0 5
\
\
\
\
9
/ \
/ \
7 22
/
10
-----------------------------------------------
Inserted: 6
Tree height: 1
Tree size: 8
Pre order traversal: [6, 5, 2, 0, 7, 9, 22, 10]
In order traversal: [0, 2, 5, 6, 7, 9, 10, 22]
Post order traversal: [0, 2, 5, 10, 22, 9, 7, 6]
Tree structure:
6
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
5 7
/ \
/ \
/ \
/ \
2 9
/ \
/ \
0 22
/
10
-----------------------------------------------
Inserted: 20
Tree height: 1
Tree size: 9
Pre order traversal: [20, 6, 5, 2, 0, 9, 7, 10, 22]
In order traversal: [0, 2, 5, 6, 7, 9, 10, 20, 22]
Post order traversal: [0, 2, 5, 7, 10, 9, 6, 22, 20]
Tree structure:
20
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
6 22
/ \
/ \
/ \
/ \
5 9
/ / \
/ / \
2 7 10
/
0
-----------------------------------------------
2. testFindElements
Tree height: 1
Tree size: 9
Pre order traversal: [20, 6, 5, 2, 0, 9, 7, 10, 22]
In order traversal: [0, 2, 5, 6, 7, 9, 10, 20, 22]
Post order traversal: [0, 2, 5, 7, 10, 9, 6, 22, 20]
Tree structure:
20
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
6 22
/ \
/ \
/ \
/ \
5 9
/ / \
/ / \
2 7 10
/
0
-----------------------------------------------
Find: 10
Tree height: 2
Tree size: 9
Pre order traversal: [10, 9, 6, 5, 2, 0, 7, 20, 22]
In order traversal: [0, 2, 5, 6, 7, 9, 10, 20, 22]
Post order traversal: [0, 2, 5, 7, 6, 9, 22, 20, 10]
Tree structure:
10
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
9 20
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
6 22
/ \
/ \
/ \
/ \
5 7
/
/
2
/
0
-----------------------------------------------
Find: 5
Tree height: 8
Tree size: 9
Pre order traversal: [5, 2, 0, 10, 6, 9, 7, 20, 22]
In order traversal: [0, 2, 5, 6, 7, 9, 10, 20, 22]
Post order traversal: [0, 2, 7, 9, 6, 22, 20, 10, 5]
Tree structure:
5
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
2 10
/ / \
/ / \
/ / \
/ / \
0 6 20
\ \
\ \
9 22
/
7
-----------------------------------------------
Find: 7
Tree height: 5
Tree size: 9
Pre order traversal: [7, 5, 2, 0, 6, 10, 9, 20, 22]
In order traversal: [0, 2, 5, 6, 7, 9, 10, 20, 22]
Post order traversal: [0, 2, 6, 5, 9, 22, 20, 10, 7]
Tree structure:
7
/ \
/ \
/ \
/ \
5 10
/ \ / \
/ \ / \
2 6 9 20
/ \
0 22
-----------------------------------------------
Find: 22
Tree height: 3
Tree size: 9
Pre order traversal: [22, 7, 5, 2, 0, 6, 20, 10, 9]
In order traversal: [0, 2, 5, 6, 7, 9, 10, 20, 22]
Post order traversal: [0, 2, 6, 5, 9, 10, 20, 7, 22]
Tree structure:
22
/
/
/
/
/
/
/
/
7
/ \
/ \
/ \
/ \
5 20
/ \ /
/ \ /
2 6 10
/ /
0 9
-----------------------------------------------
Find: 9
Tree height: 4
Tree size: 9
Pre order traversal: [9, 7, 5, 2, 0, 6, 22, 10, 20]
In order traversal: [0, 2, 5, 6, 7, 9, 10, 20, 22]
Post order traversal: [0, 2, 6, 5, 7, 20, 10, 22, 9]
Tree structure:
9
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
7 22
/ /
/ /
/ /
/ /
5 10
/ \ \
/ \ \
2 6 20
/
0
-----------------------------------------------
Find: 0
Tree height: 8
Tree size: 9
Pre order traversal: [0, 7, 2, 5, 6, 9, 22, 10, 20]
In order traversal: [0, 2, 5, 6, 7, 9, 10, 20, 22]
Post order traversal: [6, 5, 2, 20, 10, 22, 9, 7, 0]
Tree structure:
0
\
\
\
\
\
\
\
\
\
\
\
\
\
\
\
\
7
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
2 9
\ \
\ \
\ \
\ \
5 22
\ /
\ /
6 10
\
20
-----------------------------------------------
Find: 2
Tree height: 9
Tree size: 9
Pre order traversal: [2, 0, 7, 5, 6, 9, 22, 10, 20]
In order traversal: [0, 2, 5, 6, 7, 9, 10, 20, 22]
Post order traversal: [0, 6, 5, 20, 10, 22, 9, 7, 2]
Tree structure:
2
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
0 7
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
5 9
\ \
\ \
\ \
\ \
6 22
/
/
10
\
20
-----------------------------------------------
Find: 6
Tree height: 9
Tree size: 9
Pre order traversal: [6, 2, 0, 5, 7, 9, 22, 10, 20]
In order traversal: [0, 2, 5, 6, 7, 9, 10, 20, 22]
Post order traversal: [0, 5, 2, 20, 10, 22, 9, 7, 6]
Tree structure:
6
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
2 7
/ \ \
/ \ \
/ \ \
/ \ \
/ \ \
/ \ \
/ \ \
/ \ \
0 5 9
\
\
\
\
22
/
/
10
\
20
-----------------------------------------------
Find: 20
Tree height: 1
Tree size: 9
Pre order traversal: [20, 6, 2, 0, 5, 9, 7, 10, 22]
In order traversal: [0, 2, 5, 6, 7, 9, 10, 20, 22]
Post order traversal: [0, 5, 2, 7, 10, 9, 6, 22, 20]
Tree structure:
20
/ \
/ \
/ \
/ \
6 22
/ \
/ \
2 9
/ \ / \
0 5 7 10
-----------------------------------------------
3. testRemoveElement
Tree height: 1
Tree size: 9
Pre order traversal: [20, 6, 5, 2, 0, 9, 7, 10, 22]
In order traversal: [0, 2, 5, 6, 7, 9, 10, 20, 22]
Post order traversal: [0, 2, 5, 7, 10, 9, 6, 22, 20]
Tree structure:
20
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
6 22
/ \
/ \
/ \
/ \
5 9
/ / \
/ / \
2 7 10
/
0
-----------------------------------------------
Removed: 10
Tree height: 10
Tree size: 8
Pre order traversal: [9, 6, 5, 2, 0, 7, 20, 22]
In order traversal: [0, 2, 5, 6, 7, 9, 20, 22]
Post order traversal: [0, 2, 5, 7, 6, 22, 20, 9]
Tree structure:
9
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
6 20
/ \ \
/ \ \
/ \ \
/ \ \
5 7 22
/
/
2
/
0
-----------------------------------------------
Removed: 5
Tree height: 10
Tree size: 7
Pre order traversal: [2, 0, 6, 9, 7, 20, 22]
In order traversal: [0, 2, 6, 7, 9, 20, 22]
Post order traversal: [0, 7, 22, 20, 9, 6, 2]
Tree structure:
2
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
0 6
\
\
\
\
9
/ \
/ \
7 20
\
22
-----------------------------------------------
Removed: 7
Tree height: 11
Tree size: 6
Pre order traversal: [6, 2, 0, 9, 20, 22]
In order traversal: [0, 2, 6, 9, 20, 22]
Post order traversal: [0, 2, 22, 20, 9, 6]
Tree structure:
6
/ \
/ \
/ \
/ \
2 9
/ \
/ \
0 20
\
22
-----------------------------------------------
Removed: 22
Tree height: 12
Tree size: 5
Pre order traversal: [20, 6, 2, 0, 9]
In order traversal: [0, 2, 6, 9, 20]
Post order traversal: [0, 2, 9, 6, 20]
Tree structure:
20
/
/
/
/
6
/ \
/ \
2 9
/
0
-----------------------------------------------
Removed: 9
Tree height: 13
Tree size: 4
Pre order traversal: [6, 2, 0, 20]
In order traversal: [0, 2, 6, 20]
Post order traversal: [0, 2, 20, 6]
Tree structure:
6
/ \
/ \
2 20
/
0
-----------------------------------------------
Removed: 0
Tree height: 9
Tree size: 3
Pre order traversal: [2, 6, 20]
In order traversal: [2, 6, 20]
Post order traversal: [20, 6, 2]
Tree structure:
2
\
\
6
\
20
-----------------------------------------------
Removed: 2
Tree height: 13
Tree size: 2
Pre order traversal: [6, 20]
In order traversal: [6, 20]
Post order traversal: [20, 6]
Tree structure:
6
\
20
-----------------------------------------------
Removed: 6
Tree height: 12
Tree size: 1
Pre order traversal: [20]
In order traversal: [20]
Post order traversal: [20]
Tree structure:
20
-----------------------------------------------
Removed: 20
Tree height: 0
Tree size: 0
Pre order traversal: []
In order traversal: []
Post order traversal: []
Tree structure:
-----------------------------------------------
六、伸展树操作的算法复杂度
算法复杂度与二叉查询树一样,
操作 | 平均复杂度 | 最坏复杂度 |
---|---|---|
空间 | ||
查询 | ||
插入 | ||
删除 |