手撕AVL树
title: 手撕AVL树
BST
BINARY SEARCH TREE二叉查找树,代表树中节点为有序存储
所有节点都相同满足以下条件之一
1.左子节点值大于等于根节点值且右子节点值小于根节点值
2.左子节点值小于等于根节点值且右子节点值大于根节点值
的二叉树即位二叉查找树
AVL average BST 是一棵平衡的二叉查找树,通过平衡使得单次查找的时间复杂度不会超过logn
AVL Tree
平衡树的特点是,任意一个节点,其左子树的高度与右子树的高度差保持在1以内(包括1)
当出现高度差超过1的子树则进行旋转来调整,旋转时不破坏有序
旋转
右旋转
假设左边过高(一般这种过高表现为高度差为2,因为我们应在每次进行插入和删除时旋转进行维护,所以不会出现高度差为3的情况)
这时我们通过这种右旋转可以把左边过高子树的一个节点提上来,使得左子树节点高度-1,右子树节点高度+1
伪代码就是
parent = 要旋转的节点
将parent的左节点断开,再将断开的左节点的右子树断开,重新接到parent,作为parent新的左子树
将parent从他父亲断开,将parent作为他原来的左子节点(被断开的左子节点)的右子树
最后将parent原来的父亲作为此时parent新的父节点(原来的左子节点)的父亲(就是交换位置)
代码实现
public Node rightTransformAndReturn(Node node) {
System.out.println("right transform");
Node left = node.leftChild;
Node leftRightChild = left.rightChild;
if(leftRightChild!=null) {
leftRightChild.parent = node;
leftRightChild.status = LEFTSTATUS;
}
node.leftChild = leftRightChild;
left.rightChild = node;
left.parent = node.parent;
node.parent = left;
left.status = node.status;
if(node.status == LEFTSTATUS) {
left.parent.leftChild = left;
}
else if(node.status == RIGHTSTATUS){
left.parent.rightChild = left;
}
else {
root = left;
}
node.status = RIGHTSTATUS;
while(node!=null) {
node.height = Math.max(getHeight(node.leftChild)+1, getHeight(node.rightChild)+1);
node = node.parent;
}
node = left;
return node;
}
上面只分析了第一种情况,有时左子树会是这样
这时如果按照第一种的做法,此时最终结果会使得右子树高于左子树导致不平衡
原因是第一步里有 这一步
将parent的左节点断开,再将断开的左节点的右子树断开,重新接到parent,作为parent新的左子树
这一步是为了保证其有序不被破坏,所以从断开的是其左节点的右子树,也就是这颗右子树高度是不变的
而后右子树又会链接到parent,如果导致树高的不平衡是这颗右子树造成,则旋转后其仍是不平衡的(因为左边减少的高度1实际是左子树的高度)
于是我们得想办法把他变回第一种情况,做法就是,先使用左旋转其parent的左子树,将其变为下图
这时就可以使用第一种情况的方法来应对了
至于为什么只要一次右旋转因为一开始我们就讲过,高度差不会超过2,那平衡的子树的左右结点高度差就不会超过1
左旋转
此处就是右旋转的镜像实现,就不过多阐述
public Node leftTransformAndReturn(Node node) {
System.out.println("left transform");
Node right = node.rightChild;
Node rightLeftChild = right.leftChild;
if(rightLeftChild!=null) {
rightLeftChild.parent = node;
rightLeftChild.status = RIGHTSTATUS;
}
node.rightChild = rightLeftChild;
right.leftChild = node;
right.parent = node.parent;
node.parent = right;
right.status = node.status;
if(node.status == LEFTSTATUS) {
right.parent.leftChild = right;
}
else if(node.status == RIGHTSTATUS){
right.parent.rightChild = right;
}
else {
root = right;
}
node.status = LEFTSTATUS;
while(node!=null) {
node.height = Math.max(getHeight(node.leftChild)+1, getHeight(node.rightChild)+1);
node = node.parent;
}
node = right;
return node;
}
最终代码
我们将上方阐述的特殊情况(左旋转的可以联想都是对应的)总结,把对于两种情况的处理都封装
使用递归实现
public class AVLTree {
Node root;
static final int LEFTSTATUS = 0;
static final int RIGHTSTATUS = 1;
static class Node{
int num;
Node leftChild;
Node rightChild;
Node parent;
int height = 0;
int status = -1;//root
@Override
public String toString() {
return num+",height["+height+"]->left["+(leftChild==null?"null":leftChild.num)+"],->right["+(rightChild==null?"null":rightChild.num)+"]";
}
}
public void printTree(Node parent) {
if(parent!=null) {
System.out.println(parent);
printTree(parent.leftChild);
printTree(parent.rightChild);
}
}
//注意,左旋右旋会改变原来的父节点位置的节点(包括root)
public Node leftTransformAndReturn(Node node) {
System.out.println("left transform");
Node right = node.rightChild;
//双旋转
if(getHeight(right.leftChild)>getHeight(right.rightChild)) {
rightTransformAndReturn(right);
}
Node rightLeftChild = right.leftChild;
if(rightLeftChild!=null) {
rightLeftChild.parent = node;
rightLeftChild.status = RIGHTSTATUS;
}
node.rightChild = rightLeftChild;
right.leftChild = node;
right.parent = node.parent;
node.parent = right;
right.status = node.status;
if(node.status == LEFTSTATUS) {
right.parent.leftChild = right;
}
else if(node.status == RIGHTSTATUS){
right.parent.rightChild = right;
}
else {
root = right;
}
node.status = LEFTSTATUS;
while(node!=null) {
node.height = Math.max(getHeight(node.leftChild)+1, getHeight(node.rightChild)+1);
node = node.parent;
}
node = right;
return node;
}
public Node rightTransformAndReturn(Node node) {
System.out.println("right transform");
Node left = node.leftChild;
//双旋转
if(getHeight(left.rightChild)>getHeight(left.leftChild)) {
leftTransformAndReturn(left);
}
Node leftRightChild = left.rightChild;
if(leftRightChild!=null) {
leftRightChild.parent = node;
leftRightChild.status = LEFTSTATUS;
}
node.leftChild = leftRightChild;
left.rightChild = node;
left.parent = node.parent;
node.parent = left;
left.status = node.status;
if(node.status == LEFTSTATUS) {
left.parent.leftChild = left;
}
else if(node.status == RIGHTSTATUS){
left.parent.rightChild = left;
}
else {
root = left;
}
node.status = RIGHTSTATUS;
while(node!=null) {
node.height = Math.max(getHeight(node.leftChild)+1, getHeight(node.rightChild)+1);
node = node.parent;
}
node = left;
return node;
}
public int getHeight(Node node) {
//获得树高,null为-1
return node == null ? -1 : node.height;
}
public int getBF(Node node) {
//获得平衡因子---左子树高-右子树高
return getHeight(node.leftChild)- getHeight(node.rightChild);
}
public void avl(Node parent) {
if(parent == null) {
return;
}
else {
Node left = parent.leftChild;
Node right = parent.rightChild;
if(getHeight(right)>getHeight(left)+1) {
leftTransformAndReturn(parent);
}
else if(getHeight(left)>getHeight(right)+1) {
rightTransformAndReturn(parent);
}
else {
avl(parent.parent);
}
}
}
public Node addAndReturnParent(Node node,Node parent) {
if(parent == null) {
root = parent = node;
}
else {
if(parent.num>node.num) {
//right add
Node right = parent.rightChild;
if(right == null) {
node.parent = parent;
node.status = RIGHTSTATUS;
parent.rightChild = node;
if(parent.leftChild == null) {
Node temp = parent;
temp.height++;
boolean flag = true;
while((temp=temp.parent)!=null && flag) {
int oldHeight = temp.height;
temp.height = Math.max(getHeight(temp.leftChild)+1, getHeight(temp.rightChild)+1);
flag = (oldHeight != temp.height);
}
avl(parent);
}
}
else {
addAndReturnParent(node,right);
}
}
else {
//left add
Node left = parent.leftChild;
if(left == null) {
node.parent = parent;
parent.leftChild = node;
node.status = LEFTSTATUS;
if(parent.rightChild == null) {
Node temp = parent;
temp.height++;
boolean flag = true;
while((temp=temp.parent)!=null && flag) {
int oldHeight = temp.height;
temp.height = Math.max(getHeight(temp.leftChild)+1, getHeight(temp.rightChild)+1);
flag = (oldHeight != temp.height);
}
avl(parent);
}
}
else {
addAndReturnParent(node,left);
}
}
}
return parent;
}
public Node addNum(int num) {
Node node = new Node();
node.num = num;
addAndReturnParent(node, root);
return node;
}
public Node findNum(int num) {
Node node = root;
while(node!=null) {
if(node.num == num)
break;
else if(node.num<num)
node = node.leftChild;
else
node = node.rightChild;
}
return node;
}
}
节点删除
如果要删除节点,此时第一考虑树高,
1.如果是叶子节点我们可以直接删除,并调整树
2.不是叶子节点,找出一个作为替代的叶子节点,顶替其位置(只需要交换值),把问题变为第一种情况
具体的顶替查找就是,如果其左子树为空则左旋一次,变为叶子节点
如果右子树为空则右旋一次,变为叶子结点
如果都不为空,根据左右子树高度判断,
从高度大的一方查找替代后能保持有序的叶子节点(此处判断主要是为了优化减少旋转的次数,不判断选择一边也不影响,因为最终都会调整)
最后删除叶子节点后递归调整树
代码实现
public Node deleteNode(Node node) {
System.out.println("delete Node...:"+node);
if(node == null)
return node;
if(getHeight(node)==0) {
Node parent = node.parent;
node.parent = null;
if(node == root) {
root = null;
}
else if(node.status == LEFTSTATUS) {
parent.leftChild = null;
Node temp = parent;
boolean flag = true;
while((temp=temp.parent)!=null && flag) {
int oldHeight = temp.height;
temp.height = Math.max(getHeight(temp.leftChild)+1, getHeight(temp.rightChild)+1);
flag = (oldHeight != temp.height);
}
avl(parent);
}
else {
parent.rightChild = null;
Node temp = parent;
boolean flag = true;
while((temp=temp.parent)!=null && flag) {
int oldHeight = temp.height;
temp.height = Math.max(getHeight(temp.leftChild)+1, getHeight(temp.rightChild)+1);
flag = (oldHeight != temp.height);
}
avl(parent);
}
}
else {
if(node.leftChild == null) {
leftTransformAndReturn(node);
deleteNode(node);
}
else if(node.rightChild == null) {
rightTransformAndReturn(node);
deleteNode(node);
}
else if(getHeight(node.leftChild)>getHeight(node.rightChild)) {
Node leftSmall = node.leftChild;
while(leftSmall.rightChild!=null) {
leftSmall = leftSmall.rightChild;
}
node.num ^= leftSmall.num;
leftSmall.num ^= node.num;
node.num ^= leftSmall.num;
deleteNode(leftSmall);
}
else {
Node rightBig = node.rightChild;
while(rightBig.leftChild!=null) {
rightBig = rightBig.leftChild;
}
node.num ^= rightBig.num;
rightBig.num ^= node.num;
node.num ^= rightBig.num;
deleteNode(rightBig);
}
}
return node;
}
public Node deleteNum(int num) {
Node node = findNum(num);
return deleteNode(node);
}
请移步
个人主页: yangyitao.top