伸展树实现(java语言版本)
一、伸展树的基本概念
伸展树是一种二叉搜索树,伸展树也是一种平衡树,不过伸展树并不像AVL树那样对树的平衡有很严格的要求(左右孩子高度之差不能超过1),伸展树通过一系列的伸展操作,可以保证
对伸展树的任意连续M次操作
,其时间复杂度不会超过MlogN
级别,并且伸展树的实现相较于AVL树也简单了很多,不论是插入还是删除算法
二、伸展树的伸展操作
伸展树之所以能够保证MlogN的界,就是因为伸展树的伸展操作,每一次调用伸展树的search方法查找一个结点的时候,如果存在,那么就需要对该结点进行一系列的伸展操作,经过一系列的伸展操作之后,该结点最终会变成根节点
1. 左右情况的伸展
本质上就是AVL树的右双旋转
2. 左左情况的伸展
这种情况和AVL树的单右旋转不同,需要先将A进行单向左旋,然后再将B进行单向左旋
3. 右左情况的伸展
略,情况同1
4. 右右情况的伸展
略,情况同2
三、伸展树的插入
伸展树的插入就是普通BST的插入算法,无差别
四、伸展树的删除
伸展树的删除步骤如下
(1)找到被删除的结点D的左子树DL的最大结点M,然后将M结点伸展到DL的根节点处
(2)然后将D删除,因为M一定没有右孩子,即使被伸展到DL的根节点也不会有右孩子,此时只需要将D的右子树DR挂到M的右孩子上即可
三、伸展树代码实现
package splaytree;
/**
* 实现伸展树
*
* @author 西城风雨楼
*/
public class SplayTree<K extends Comparable<K>, V> {
private TreeNode<K, V> root;
/**
* 向伸展树中插入一个新的结点
*
* @param key 关键字
* @param value 值
*/
public void insert(K key, V value) {
TreeNode<K, V> cur = root;
TreeNode<K, V> pre = null;
while (cur != null) {
int cmp = cur.key.compareTo(key);
if (cmp < 0) {
pre = cur;
cur = cur.right;
} else if (cmp > 0) {
pre = cur;
cur = cur.left;
} else {
// 如果相等,那么就进行替换
cur.value = value;
break;
}
}
TreeNode<K, V> node = new TreeNode<>(key, value);
node.setParent(pre);
if (pre == null) {
// 说明当前插入的结点是根节点
root = node;
return;
}
// 如果pre不为null,那么说明cur不为null即
// root不为null,所以pre一定指向的是bst中
// node需要插入位置的前驱结点
if (pre.key.compareTo(key) < 0) {
pre.right = node;
} else {
pre.left = node;
}
}
/**
* 返回先序遍历
*
* @return 返回先序遍历序列字符串
*/
public String preOrder() {
StringBuilder pre = new StringBuilder();
if (root == null) {
return pre.toString();
}
TreeNode<K, V> cur = root;
while (cur != null) {
if (cur.left != null) {
TreeNode<K, V> mostRight = cur.left;
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
if (mostRight.right == null) {
pre.append("{")
.append(cur.key)
.append(",")
.append(cur.value)
.append(",")
.append(cur.parent == null ? "null" : cur.parent.value)
.append("}\t");
mostRight.right = cur;
cur = cur.left;
continue;
} else {
mostRight.right = null;
}
} else {
pre.append("{")
.append(cur.key)
.append(",")
.append(cur.value)
.append(",")
.append(cur.parent == null ? "null" : cur.parent.value)
.append("}\t");
}
cur = cur.right;
}
return pre.toString();
}
public String inOrder() {
StringBuilder in = new StringBuilder();
if (root == null) {
return in.toString();
}
TreeNode<K, V> cur = root;
while (cur != null) {
if (cur.left != null) {
TreeNode<K, V> mostRight = cur.left;
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
if (mostRight.right == null) {
mostRight.right = cur;
cur = cur.left;
continue;
} else {
mostRight.right = null;
in.append("{")
.append(cur.key)
.append(",")
.append(cur.value)
.append(",")
.append(cur.parent == null ? "null" : cur.parent.value)
.append("}\t");
}
} else {
in.append("{")
.append(cur.key)
.append(",")
.append(cur.value)
.append(",")
.append(cur.parent == null ? "null" : cur.parent.value)
.append("}\t");
}
cur = cur.right;
}
return in.toString();
}
/**
* 删除伸展树指定的关键字,基本的删除算法的思想是
* 首先查找这个被删除的元素,然后这个元素就会变成根元素
* 然后删除这个根元素,同时查找其左子树的最大值,这个最大值
* 也会变成根元素,这个根元素一定右子树为空,那么把这个根元素的
*
* @param key 关键字
* @return 返回被删除的关键字对应的值
*/
public V delete(K key) {
TreeNode<K, V> node = searchHelper(key);
if (node == null) {
return null;
}
// 找到node的左孩子的最大值
TreeNode<K, V> leftMax = findMax(node.left);
// 将左孩子的最大值伸展到根节点,leftMax一定是没有右孩子的
// 因为leftMax的伸展百分之百是left-left型,伸展之后是right-right型
// 后面的伸展只能在这两个型之间跳跃
splaying(leftMax);
leftMax.right = node.right;
node.left = null;
node.right = null;
return node.value;
}
private TreeNode<K, V> findMax(TreeNode<K, V> root) {
while (root != null && root.right != null) {
root = root.right;
}
return root;
}
/**
* 单向右旋转
*
* @param node 被旋转的结点
*/
private void singleRightRotate(TreeNode<K, V> node) {
// 特别注意在旋转过程中更新结点的父亲指针,否则将会导致回溯
// 过程中产生某些意外情况
TreeNode<K, V> left = node.left;
node.left = left.right;
if (left.right != null) {
left.right.parent = node;
}
left.parent = node.parent;
if (node.parent == null) {
// 如果当前旋转的是根节点
root = left;
} else if (node == node.parent.left) {
node.parent.left = left;
} else {
node.parent.right = left;
}
left.right = node;
node.parent = left;
}
/**
* 单向左旋转
*
* @param node 被旋转的结点
*/
private void singleLeftRotate(TreeNode<K, V> node) {
TreeNode<K, V> right = node.right;
node.right = right.left;
if (right.left != null) {
right.left.parent = node;
}
right.parent = node.parent;
if (node.parent == null) {
root = right;
} else if (node == node.parent.left) {
node.parent.left = right;
} else {
node.parent.right = right;
}
right.left = node;
node.parent = right;
}
/**
* 双向右旋转
*
* @param node 被旋转的结点
*/
private void doubleRightRotate(TreeNode<K, V> node) {
singleLeftRotate(node.left);
singleRightRotate(node);
}
/**
* 双向左旋转
*
* @param node 被旋转的结点
*/
private void doubleLeftRotate(TreeNode<K, V> node) {
singleRightRotate(node.right);
singleLeftRotate(node);
}
/**
* 根据关键字查找指定的值,每一次查找都会导致被查找的结点
* 变成根结点,并且其他结点的位置深度基本上下降一半
*
* @param key 关键字
* @return 返回key对应的值
*/
public V search(K key) {
TreeNode<K, V> target = searchHelper(key);
return target == null ? null : target.value;
}
/**
* 从某个结点开始展开,具体的展开规则是:
* (1)node.parent == root && node.parent.left == node,
* 那么将node.parent直接右旋,这样node将会成为根节点
* (2)node.parent == root && node.parent.right == node,
* 那么将node.parent直接左旋,这样node将会成为根节点
* (3)node.parent != root,node.parent == node.parent.left
* && node == node.parent.right,那么将node.parent.parent双向右旋
* (4)node.parent != root,node.parent == node.parent.right
* && node == node.parent.left,那么将node.parent.parent双向左旋
* (5)node.parent != root,node.parent == node.parent.left
* && node == node.parent.left,那么将node.parent.parent单向右旋,
* 然后将node.parent单向右旋
* (6)node.parent != root,node.parent == node.parent.right
* && node == node.parent.right,那么将node.parent.parent单向左旋,
* 然后将node.parent单向左旋
*
* @param node 待旋转的结点
*/
private void splaying(TreeNode<K, V> node) {
while (node.parent != null) {
if (node.parent == root) {
if (node.parent.left == node) {
// 情况(1)
singleRightRotate(node.parent);
} else {
// 情况(2)
singleLeftRotate(node.parent);
}
} else if (node.parent == node.parent.parent.left) {
if (node == node.parent.right) {
// 情况(3)
doubleRightRotate(node.parent.parent);
} else {
// 情况(5)
singleRightRotate(node.parent.parent);
singleRightRotate(node.parent);
}
} else {
if (node == node.parent.right) {
// 情况(6)
singleLeftRotate(node.parent.parent);
singleLeftRotate(node.parent);
} else {
// 情况(4)
doubleLeftRotate(node.parent.parent);
}
}
}
}
/**
* 搜索SplayTree中具有指定关键字的结点
*
* @param key 关键字
* @return 返回关键字和key相等的结点,如果不存在,返回null
*/
private TreeNode<K, V> searchHelper(K key) {
TreeNode<K, V> cur = root;
while (cur != null) {
int cmp = cur.key.compareTo(key);
if (cmp < 0) {
cur = cur.right;
} else if (cmp > 0) {
cur = cur.left;
} else {
break;
}
}
// 可能从break跳出,也可能完全没进入while循环
if (cur != null) {
splaying(cur);
}
return cur;
}
/**
* 伸展树的结点类型
*
* @param <K> 关键字类型
* @param <V> 值类型
*/
private static class TreeNode<K extends Comparable<K>, V> {
public K key;
public V value;
public TreeNode<K, V> left;
public TreeNode<K, V> right;
public TreeNode<K, V> parent;
public TreeNode(K key, V value) {
this.key = key;
this.value = value;
}
public void setParent(TreeNode<K, V> parent) {
this.parent = parent;
}
}
public static void main(String[] args) {
SplayTree<Integer, Integer> splayTree = new SplayTree<>();
splayTree.insert(1, 1);
splayTree.insert(32, 32);
splayTree.insert(30, 30);
splayTree.insert(31, 31);
splayTree.insert(28, 28);
splayTree.insert(29, 29);
splayTree.insert(26, 26);
splayTree.insert(27, 27);
splayTree.insert(24, 24);
splayTree.insert(25, 25);
splayTree.insert(22, 22);
splayTree.insert(23, 23);
splayTree.insert(20, 20);
splayTree.insert(21, 21);
splayTree.insert(18, 18);
splayTree.insert(19, 19);
splayTree.insert(16, 16);
splayTree.insert(17, 17);
splayTree.insert(14, 14);
splayTree.insert(15, 15);
splayTree.insert(12, 12);
splayTree.insert(13, 13);
splayTree.insert(10, 10);
splayTree.insert(11, 11);
splayTree.insert(8, 8);
splayTree.insert(9, 9);
splayTree.insert(6, 6);
splayTree.insert(7, 7);
splayTree.insert(4, 4);
splayTree.insert(5, 5);
splayTree.insert(2, 2);
splayTree.insert(3, 3);
System.out.println(splayTree.preOrder());
System.out.println(splayTree.inOrder());
System.out.println(splayTree.search(2));
System.out.println(splayTree.search(3));
System.out.println(splayTree.search(4));
System.out.println(splayTree.search(5));
System.out.println(splayTree.search(6));
System.out.println(splayTree.search(7));
System.out.println(splayTree.search(8));
System.out.println(splayTree.search(9));
System.out.println("访问部分结点后遍历:");
System.out.println(splayTree.preOrder());
System.out.println(splayTree.inOrder());
}
}