一)伸展二叉树简介
规则1、和二叉搜索树性质一样,比节点小的值排列在左边,比节点大的值排列在右边。
规则2、当某个节点被访问时,伸展树会通过旋转使该节点成为树根。方便多次查找。
二)伸展二叉树(采用自顶向下旋转)
第一步:定义伸展二叉树结构
/**
* 伸展二叉树
* @author ouyangjun
*/
public class SplayTree<T extends Comparable<T>> {
// 伸展二叉树结构
static class Node<T> {
Node<T> left; // 左节点
Node<T> right; // 右节点
T value; // 节点值(关键字)
Node (Node<T> left, Node<T> right, T value) {
this.left = left;
this.right = right;
this.value = value;
}
}
private Node<T> root; // 根节点
public Node<T> getRoot() {
return root;
}
}
第二步:旋转,包括左左旋转和右右旋转
// 左左旋转
private Node<T> leftleft(Node<T> node) {
Node<T> newNode = node.left; // 把node节点下的左边节点先用一个临时变量存储
node.left = newNode.right; // 把newNode下的右节点作为node的左节点
newNode.right = node; // 把node节点作为现有newNode节点下的右节点
// 返回
return newNode;
}
// 右右旋转
private Node<T> rightright(Node<T> node) {
Node<T> newNode = node.right; // 把node节点下的右边节点先用一个临时变量存储
node.right = newNode.left; // 把newNode下的左节点作为node的右节点
newNode.left = node; // 把node节点作为newNode节点下的左节点
// 返回
return newNode;
}
// 旋转
private Node<T> splay(Node<T> node, T value) {
if (value == null) {
return null;
}
Node<T> splayNode = new Node<T>(null, null, value);
// 调用旋转节点方法
return splay(node, splayNode);
}
private Node<T> splay(Node<T> node, Node<T> splayNode) {
if (node == null || splayNode == null) {
// 返回根节点
return root;
}
Node<T> nullNode = new Node<T>(null, null, null); // 记录中树的节点
Node<T> leftNode = nullNode; // 记录小于node的节点
Node<T> rightNode = nullNode; // 记录大于node的节点
// 判断
while (node != null) {
// 判断
int num = splayNode.value.compareTo(node.value); // 把value和node的value比较
if (num < 0) { // 在左边
if (node.left == null) {
break;
}
// splayNode小于node
if (splayNode.value.compareTo(node.left.value) < 0) {
// 进行了AVL中的左左旋转
node = leftleft(node);
if (node.left == null) {
break;
}
}
leftNode.left = node;
leftNode = node;
node = node.left; // 对节点进行了右旋转
} else if (num > 0) { // 在右边
if (node.right == null) {
break;
}
// splayNode大于node
if (splayNode.value.compareTo(node.right.value) > 0) {
// 进行了AVL中的右右旋转
node = rightright(node);
if (node.right == null) {
break;
}
}
rightNode.right = node;
rightNode = node;
node = node.right; // 对节点进行了左旋转
} else {
// 相等, 从二叉树中找到开始旋转的节点了, 直接跳出
break;
}
}
// 把leftNode节点和rightNode节点合并到node
leftNode.left = node.right;
rightNode.right = node.left;
node.left = nullNode.right;
node.right = nullNode.left;
// 重新存储节点
root = node;
return node;
}
第三步:新增节点
// 新增节点
public Node<T> add(T value) {
if (value == null) {
return null;
}
Node<T> newNode = new Node<T>(null, null, value);
// 根节点
Node<T> rootNode = add(root, newNode);
// 调用旋转节点方法
return splay(rootNode, newNode);
}
private Node<T> add(Node<T> node, Node<T> newNode) {
Node<T> current = null; // 需要插入的节点
// 判断节点是否为空
while (node != null) {
current = node;
// 判断
int num = newNode.value.compareTo(node.value); // 把value和treeNode的value比较
if (num < 0) { // 在左边
node = node.left;
} else if (num > 0) { // 在右边
node = node.right;
} else { // 相等, 节点重复了, 直接返回
return node;
}
}
if (current == null) { // 如果没有找到插入节点,把新节点作为根节点
root = newNode;
} else {
int num = newNode.value.compareTo(current.value);
if (num < 0) { // 在左边新增
current.left = newNode;
} else if (num > 0) { // 在右边新增
current.right = newNode;
}
}
// 返回
return root;
}
第四步:辅助方法
// 查找节点是否存在
private boolean search(Node<T> node, T value) {
if (node != null) {
int num = value.compareTo(node.value);
if (num < 0) {
return search(node.left, value);
} else if(num > 0) {
return search(node.right, value);
} else {
return true;
}
}
return false;
}
/**
* 前序遍历: 根节点 ==> 左节点 ==> 右节点
* @param node
*/
public void preNode(Node<T> node) {
if (node != null) {
System.out.print(node.value + "\t");
preNode(node.left);
preNode(node.right);
}
}
/**
* 中序遍历: 左节点 ==> 根节点 ==> 右节点
* @param node
*/
public void inNode(Node<T> node) {
if (node != null) {
inNode(node.left);
System.out.print(node.value + "\t");
inNode(node.right);
}
}
/**
* 后序遍历: 左节点 ==> 右节点 ==> 根节点
* @param node
*/
public void nextNode(Node<T> node) {
if (node != null) {
nextNode(node.left);
nextNode(node.right);
System.out.print(node.value + "\t");
}
}
第五步:删除节点
// 删除节点
public Node<T> remove(T value) {
return remove(root, value);
}
private Node<T> remove(Node<T> node, T value) {
if (node == null || value == null) {
return null;
}
// 查找键值为key的节点,找不到的话直接返回。
if (!search(node, value)) {
return node;
}
Node<T> x;
// 将key对应的节点旋转为根节点。
node = splay(node, value);
if (node.left != null) {
// 将"tree的前驱节点"旋转为根节点
x = splay(node.left, value);
// 移除tree节点
x.right = node.right;
} else {
x = node.right;
}
node = null;
// 重新保存根节点
root = x;
return x;
}
识别二维码关注个人微信公众号
本章完结,待续,欢迎转载!
本文说明:该文章属于原创,如需转载,请标明文章转载来源!