avl树
AVL树本质上还是一棵二叉搜索树,它的特点是:
1.本身首先是一棵二叉搜索树。
2.带有平衡条件:每个结点的左右子树的高度之差的绝对值(平衡因子)最多为1。
也就是说,AVL树,本质上是带了平衡功能的二叉查找树(二叉排序树,二叉搜索树)
问题解析:
为什么会出现avl树,二叉树中不断的新增元素,但是由于顺序问题造成最终树的形态"畸形",导致搜索的时间复杂度过高,寻求一种方法在新增元素时,通过树中的边旋转,然后得到一个较为平衡的树,即两个子树的度之差小于2
其中涉及到问题:为什么旋转,怎么旋转,有什么特征进行判断
定义节点信息如下
class Node {
public int data; //本节点携带数据
public Node leftChild; //左子树节点
public Node rightChild; //右子树节点
public Node parent; //父节点
public int depth; //深度 该节点距离叶节点的最大距离
public int dep_sub; //子树深度差 右子树的depth 减 左子树的depth
}
1.为什么旋转
在树新增元素的时候会遇到左右不对称的情况
2.怎么旋转
其实我在看这个算法的时候,不晓得什么叫左旋转,什么叫右旋转,以下为我自己参照网上教程上的定义:
我总结出来就左旋转和右旋转,而一些教程中的左右旋转,右左旋转就是一些组合.
图形速记
左旋转:较高节点为偏左,较高节点向下旋转
右旋转:较高节点偏右,较高节点向下旋转
而在新增节点时我们会遇到以下四类
①左左情况
某个节点的左子树的左子树上新增,或新增某个子节点,导致当前的节点的右子树与左子树深度之差大于等于-2
以第一种情况为例
这个时候有的小伙伴就要问了,这个只是因为X节点中没有右子树,有右子树咋办嘞?这里如果X有右子树的话,我们就将X的右子树赋给Y的左子树,向第二个例子一样
(实际上有没有右子树是一样的,都是将X.rightChild赋值给Y.liftChild)
对于左左状态,我的记忆方法就是左左子树的新增出现问题,此时需要对子树差不符合规范的节点(dep_sub<=2)进行右旋操作
②右右情况
和左左情况一样
③左右情况
这种情况的产生是因为左子树的右子树出现了问题,我们需要先进行左旋转,后进行右旋转.从而达到平衡状态
对于第一种状态
对于第二种状态
④右左情况:
这种情况的产生是因为右子树的左子树出现了问题,我们需要先进行右旋转,后进行左旋转,从而达到平衡状态
状态示例
对于第一种情况
对于第二种情况
那么如何进行判定这四种情况呢?
这四种情况取决于节点的左右子树深度差(dep_sub)
if (node.dep_sub > 1) {
// 右比左长,判断右子树中那条路出问题
if (node.rightChild.dep_sub > 0) {
// 右右情况,进行左旋
leftRotate(node, node.rightChild);
} else if (node.rightChild.dep_sub < 0) {
// 右左情况,进行右旋,再进行左旋
rightRotate(node.rightChild.leftChild, node.rightChild);
leftRotate(node, node.rightChild);
} else {
// 右中情况 左旋 在删除节点时出现
leftRotate(node, node.rightChild);
}
} else if (node.dep_sub < -1) {
// 左比右长,判断左子树出现过长
if (node.leftChild.dep_sub < 0) {
// 左左情况,进行右旋
rightRotate(node.leftChild, node);
} else if (node.leftChild.dep_sub > 0) {
// 左右情况,进行左旋,再进行右旋
leftRotate(node.leftChild, node.leftChild.rightChild);
rightRotate(node.leftChild, node);
} else {
// 左中情况 右旋 在删除节点时出现
rightRotate(node.leftChild, node);
}
}
这是这四种方案的不同处理方案,接下来是对于此种树的具体java代码实现
删除节点
而我们在删除时,可能遇到的情况有
以下几种情况:
1.删除节点为叶子节点
直接删除该被删除节点的父节点引用
2.删除节点有且只有一个子节点
将该子节点直接与被删除节点的父节点进行建立连接
3.删除节点有两个子节点
考虑哪边的子树较长,将较长子树向上移动,使用较长子树值最靠近删除数据的值,一般是左子树的最右子树或右子树的最左子树
顶替删除数据节点的位置例如:
以下为删除的具体代码:
public boolean del(int data) {
Node dataNode = find(data);
if (dataNode == null) {
// 未找到
return false;
}
//
if (dataNode.leftChild == null && dataNode.rightChild == null) {
// 若有无子节点
changeParentQuate(dataNode, null);
updateDep(dataNode.parent);
} else if (dataNode.leftChild != null && dataNode.rightChild == null) {
// 有一个子节点,且是左子树.需要建立dataNode左子树与dataNode的键连接
changeParentQuate(dataNode, dataNode.leftChild);
dataNode.leftChild.parent = dataNode.parent;
updateDep(dataNode.parent);
} else if (dataNode.leftChild == null && dataNode.rightChild != null) {
// 有一个子节点,且是右子树
changeParentQuate(dataNode, dataNode.rightChild);
dataNode.rightChild.parent = dataNode.parent;
updateDep(dataNode.parent);
} else {
// 有两个子节点
if(dataNode.dep_sub>=0) {
// 右子树长或等长
// 判断右子树有没有左孩子
Node xNode = dataNode.rightChild;
if(xNode.leftChild==null) {
// 没有左孩子就直接占用父位
changeParentQuate(dataNode, xNode);
xNode.parent=dataNode.parent;
xNode.leftChild = dataNode.leftChild;
xNode.leftChild.parent = xNode;
updateDep(xNode);
}else {
//找到最左孩子
while(xNode.leftChild!=null) {
xNode = xNode.leftChild;
}
//更新键
xNode.parent.leftChild = xNode.rightChild;
if(xNode.rightChild!=null) {
xNode.rightChild.parent = xNode.parent;
}
Node pNode = xNode.parent;
changeParentQuate(dataNode, xNode);
xNode.parent = dataNode.parent;
xNode.leftChild = dataNode.leftChild;
xNode.leftChild.parent = xNode;
xNode.rightChild = dataNode.rightChild;
dataNode.rightChild.parent = xNode;
//更新索引 从原xNode的父节点开始
updateDep(pNode);
}
}else {
//左子树长
Node xNode = dataNode.leftChild;
if(xNode.rightChild==null) {
//无最右节点 直接往上顶
changeParentQuate(dataNode, xNode);
xNode.parent = dataNode.parent;
xNode.rightChild = dataNode.rightChild;
xNode.rightChild.parent = xNode;
updateDep(xNode);
}else {
while(xNode.rightChild!=null) {
xNode=xNode.rightChild;
}
Node pNode = xNode.parent;
//更新键
xNode.parent.rightChild = xNode.leftChild;
if(xNode.leftChild!=null) {
xNode.leftChild.parent = xNode.parent;
}
changeParentQuate(dataNode, xNode);
xNode.parent = dataNode.parent;
xNode.leftChild = dataNode.leftChild;
xNode.leftChild.parent = xNode;
xNode.rightChild = dataNode.rightChild;
xNode.rightChild.parent = xNode;
updateDep(pNode);
}
}
}
return true;
}
import java.util.Stack;
public class Tree {
public Node root = null;
class Node {
public int data;
public Node leftChild;
public Node rightChild;
public Node parent;
public int depth;
public int dep_sub;
public Node() {
this.leftChild = null;
this.rightChild = null;
this.parent = null;
this.depth = 0;
this.dep_sub = 0;
}
public Node(int data) {
this.leftChild = null;
this.rightChild = null;
this.parent = null;
this.depth = 0;
this.dep_sub = 0;
this.data = data;
}
public Node(Node parent, int data) {
this.leftChild = null;
this.rightChild = null;
this.depth = 0;
this.dep_sub = 0;
this.parent = parent;
this.data = data;
}
public void setDep_sub() {
int left = this.leftChild == null ? 0 : this.leftChild.depth;
int right = this.rightChild == null ? 0 : this.rightChild.depth;
// 更新深度
this.depth = Math.max(left, right) + 1;
// 更新深度差
this.dep_sub = right - left;
}
public void setDepth() {
int left = this.leftChild == null ? 0 : this.leftChild.depth;
int right = this.rightChild == null ? 0 : this.rightChild.depth;
// 更新深度
this.depth = Math.max(left, right) + 1;
}
}
// 构造方法
public Tree() {
}
public Tree(Node root) {
this.root = root;
}
// 功能设置判空函数
public boolean isEmpty() {
if (this.root == null) {
return true;
} else {
return false;
}
}
@Override
public String toString() {
String str = "";
Node xNode = root;
Node pNode = null;
Stack<Node> nodes = new Stack<>();
do {
while (xNode != null) {
nodes.push(xNode);
xNode = xNode.leftChild;
}
pNode = nodes.pop();
str += pNode.data + ",";
xNode = pNode.rightChild;
} while (!nodes.isEmpty() || xNode != null);
return str;
}
/**
* 将data插入树中
*
* @param data
*/
public void add(int data) {
Node xNode = root;
Node newNode = new Node(data);
newNode.depth = 1;
if (isEmpty()) {
// 插入到根节点,此时root的P为null
root = newNode;
} else {
while (true) {
// 循环指导找到插入位置
if (data < xNode.data) {
if (xNode.leftChild == null) {
xNode.leftChild = newNode;
newNode.parent = xNode;
break;
} else {
xNode = xNode.leftChild;
}
} else if (data > xNode.data) {
if (xNode.rightChild == null) {
xNode.rightChild = newNode;
newNode.parent = xNode;
break;
} else {
xNode = xNode.rightChild;
}
}
}
// 找到插入位置,更新索引
updateDep(newNode);
}
}
/**
* 从node节点开始向上,开始更新深度和深度差,如果出现不平衡现象,进行旋转操作
*
* @param node
*/
private void updateDep(Node node) {
if(node==null) {
return;
}
node.setDep_sub();
Node pNode = node.parent;
if (node.dep_sub > 1) {
// 右比左长,判断右子树中那条路出问题
if (node.rightChild.dep_sub > 0) {
// 右右情况,进行左旋
leftRotate(node, node.rightChild);
} else if (node.rightChild.dep_sub < 0) {
// 右左情况,进行右旋,再进行左旋
rightRotate(node.rightChild.leftChild, node.rightChild);
leftRotate(node, node.rightChild);
} else {
// 右中情况 左旋
leftRotate(node, node.rightChild);
}
} else if (node.dep_sub < -1) {
if (node.leftChild.dep_sub < 0) {
// 左左情况,进行右旋
rightRotate(node.leftChild, node);
} else if (node.leftChild.dep_sub > 0) {
// 左右情况,进行左旋,再进行右旋
leftRotate(node.leftChild, node.leftChild.rightChild);
rightRotate(node.leftChild, node);
} else {
// 左中情况 右旋
rightRotate(node.leftChild, node);
}
}
updateDep(pNode);
}
/**
* 左旋算法,将X旋转至Y的左子树
*
* @param X
* @param Y
*/
private void leftRotate(Node X, Node Y) {
if (X.parent == null) {
// 根节点
root = Y;
}
Y.parent = X.parent;
// 将X的父亲指向Y
changeParentQuate(X, Y);
X.parent = Y;
X.rightChild = Y.leftChild;
if (X.rightChild != null) {
X.rightChild.parent = X;
}
Y.leftChild = X;
// 更新深度和深度差
X.setDep_sub();
Y.setDep_sub();
System.out.print("左旋");
}
/**
* 右旋算法,将Y旋转至X的右子树
*
* @param X
* @param Y
*/
private void rightRotate(Node X, Node Y) {
if (Y.parent == null) {
root = X;
}
// 更新父节点的引用
changeParentQuate(Y, X);
X.parent = Y.parent;
Y.parent = X;
Y.leftChild = X.rightChild;
if (Y.leftChild != null) {
Y.leftChild.parent = Y;
}
X.rightChild = Y;
// 更新深度和深度差
Y.setDep_sub();
X.setDep_sub();
System.out.print("右旋");
}
public boolean del(int data) {
Node dataNode = find(data);
if (dataNode == null) {
// 未找到
return false;
}
//
if (dataNode.leftChild == null && dataNode.rightChild == null) {
// 若有无子节点
changeParentQuate(dataNode, null);
updateDep(dataNode.parent);
} else if (dataNode.leftChild != null && dataNode.rightChild == null) {
// 有一个子节点,且是左子树.需要建立dataNode左子树与dataNode的键连接
changeParentQuate(dataNode, dataNode.leftChild);
dataNode.leftChild.parent = dataNode.parent;
updateDep(dataNode.parent);
} else if (dataNode.leftChild == null && dataNode.rightChild != null) {
// 有一个子节点,且是右子树
changeParentQuate(dataNode, dataNode.rightChild);
dataNode.rightChild.parent = dataNode.parent;
updateDep(dataNode.parent);
} else {
// 有两个子节点
if(dataNode.dep_sub>=0) {
// 右子树长或等长
// 判断右子树有没有左孩子
Node xNode = dataNode.rightChild;
if(xNode.leftChild==null) {
// 没有左孩子就直接占用父位
changeParentQuate(dataNode, xNode);
xNode.parent=dataNode.parent;
xNode.leftChild = dataNode.leftChild;
xNode.leftChild.parent = xNode;
updateDep(xNode);
}else {
//找到最左孩子
while(xNode.leftChild!=null) {
xNode = xNode.leftChild;
}
//更新键
xNode.parent.leftChild = xNode.rightChild;
if(xNode.rightChild!=null) {
xNode.rightChild.parent = xNode.parent;
}
Node pNode = xNode.parent;
changeParentQuate(dataNode, xNode);
xNode.parent = dataNode.parent;
xNode.leftChild = dataNode.leftChild;
xNode.leftChild.parent = xNode;
xNode.rightChild = dataNode.rightChild;
dataNode.rightChild.parent = xNode;
//更新索引 从原xNode的父节点开始
updateDep(pNode);
}
}else {
//左子树长
Node xNode = dataNode.leftChild;
if(xNode.rightChild==null) {
//无最右节点 直接往上顶
changeParentQuate(dataNode, xNode);
xNode.parent = dataNode.parent;
xNode.rightChild = dataNode.rightChild;
xNode.rightChild.parent = xNode;
updateDep(xNode);
}else {
while(xNode.rightChild!=null) {
xNode=xNode.rightChild;
}
Node pNode = xNode.parent;
//更新键
xNode.parent.rightChild = xNode.leftChild;
if(xNode.leftChild!=null) {
xNode.leftChild.parent = xNode.parent;
}
changeParentQuate(dataNode, xNode);
xNode.parent = dataNode.parent;
xNode.leftChild = dataNode.leftChild;
xNode.leftChild.parent = xNode;
xNode.rightChild = dataNode.rightChild;
xNode.rightChild.parent = xNode;
updateDep(pNode);
}
}
}
return true;
}
/**
* 将node父指针指向node更改为指向xNode
* @param node
* @param xNode
*/
public void changeParentQuate(Node node, Node xNode) {
if (node.parent != null) {
if (node.parent.leftChild == node) {
node.parent.leftChild = xNode;
} else {
node.parent.rightChild = xNode;
}
} else {
root = xNode;
}
}
/**
* 找到data对应的节点并返回
* @param data 数据
* /
public Node find(int data) {
Node xNode = root;
while (xNode != null) {
if (data == xNode.data) {
break;
} else if (data < xNode.data) {
// 进入左子树
xNode = xNode.leftChild;
} else if (data > xNode.data) {
// 进入右子树
xNode = xNode.rightChild;
}
}
return xNode;
}
/**
* 前序遍历
* @param node 当前的节点
* /
public void followUp(Node node) {
if (node != null) {
System.out.print(node.data + ",");
followUp(node.leftChild);
followUp(node.rightChild);
}
}
}
附上测试代码
//根据先序遍历和中序遍历可以确定我们的树结构
public static void main(String[] args) {
Tree tree = new Tree();
int[] arr = { 12, 4, 1, 3, 7, 8, 10, 9, 2, 11, 6, 5 };
for (int a : arr) {
System.out.println("新增:" + a);
tree.add(a);
System.out.println("");
System.out.println("中序遍历:" + tree.toString());
System.out.print("前序遍历:");
tree.followUp(tree.root);
System.out.println();
}
int[] arr2 = { 1, 8, 9, 2, 6, 7 };
for (int a : arr2) {
System.out.println("\n删除:" + a);
tree.del(a);
System.out.println("中序遍历:" + tree.toString());
System.out.print("前序遍历:");
tree.followUp(tree.root);
}
}