第十二课 常见的二叉树:基于二叉搜索树的伸展树

第十二课 常见的二叉树:基于二叉搜索树的伸展树

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));
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java封神之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值