AVL树
AVL树的概念
基于二叉搜索树上的优化,它解决了如果数据有序或者接近有序的二叉搜索树将退化为单链表查找元素相当于在顺序表中搜索元素,效率低下。故AVL树当向二叉搜索树中插入新的节点,能够保持每个节点的左右子树高度之差的绝对值不超过1,从而减少平均搜索的长度,
特点
①叶子节点的最大高度相差不会超过1 平衡二叉树
②对于任意一个节点,左子树和右子树的高度差不能超过1
③平横二叉树的高度和节点的数量之间的关系也是O(logn)的
④AVL常常用于查询较多的情况下
⑤他的左右子树均为AVL树
⑥如果一棵二叉树是高度平衡的,他就是AVL树,如果他有n个节点,其高度可保持在 O ( l o g 2 N ) O(log_{2}N) O(log2N)搜索的时间复杂度为 O ( l o g 2 N ) O(log_{2}N) O(log2N)
⑦AVL树中的任意节点的BF只可能是-1 0 1;
如下图为一颗AVL树:
AVL的实现
实现AVL树的逻辑
- 首先在
AVLTree
中定义一个节点内部类Node; - 构造函数初始化AVL树
- 基于二分搜索树的实现AVL树
实现Node节点的代码
private class Node {
public K key;
public V value;
public Node left, right;
public int height;
public Node(K key, V value) {
this.key = key;
this.value = value;
left = null;
right = null;
height = 1;
}
}
定义一个节点主要存储他的key和value (一般只用value 就可以这里复用了BSTMap的结构算法)
对
AVLTree
树的初始化
//私有成员变量root和size并在构造函数里面初始化操作
private Node root;
private int size;
public AVLTree() {
root = null;
size = 0;
}
判断该树是否为一个二分搜索树(这个可以省略)
//判断是否为二分搜索树
public boolean isBST() {
ArrayList<K> keys = new ArrayList<>();
//判断是否有序
inOrder(root, keys);
//由上一步可以的出整棵树的顺序均在keys容器中存放
//遍历容器让后者根前者比较如果后者小于前者则证明不是个二分搜索树
for (int i = 0; i < keys.size(); i++) {
if (keys.get(i - 1).compareTo(keys.get(i)) > 0)
return false;
}
return true;
}
//中序遍历法:如果有序则结果也有序
private void inOrder(Node node, ArrayList<K> keys) {
if (node == null) {
return;
}
inOrder(node.left, keys);
//将节点数据拿出向ArrayList中添加keys值便于isBST判断
keys.add(node.key);
inOrder(node.right, keys);
}
一些辅助函数
contains() --set()---get()
minimun()
得到对应子树下的最小的节点值属于一个递归函数
contains(K key)
该函数主要证明所查的节点是否在该树内
get(K key)
得到键值为key得value值(对于AVL树来说可以不需要)
set(K key ,V newValue)
将键值为key得地方的值修改为value 如果存在进行覆盖
//拿到传入节点的最小的值
private Node minimum(Node node) {
//递归终止的条件最左边为null
if (node.left == null) {
return node;
}
//进入递归将左子树的左子树传入
return minimum(node.left);
}
public boolean contains(K key) {
return getNode(root, key) != null;
}
public V get(K key) {
Node node = getNode(root, key);
return node == null ? null : node.value;
}
public void set(K key, V newValue) {
Node node = getNode(root, key);
if (node == null) {
throw new IllegalArgumentException(key + "doesn't exist!");
}
node.value = newValue;
}
//得到数的元素个数
public int getSize(){
return size;
}
public boolean isEmpty(){
return size==0;
}
//得到以node为节点树的高度
private int getHeight(Node node){
if(node==null){
return 0;
}
return node.height;
}
辅助函数判断该树是否为一颗二叉平衡树
这里可以创建一个isBalanced()
函数通过递归调用最终的出结果
public boolean isBalanced(){
return isBalanced(root);
}
private boolean isBalanced(Node node) {
//递归终止的条件只有当节点为空的时候返回true
if (node == null)
return true;
//得到当前节点的平衡因数;
int balanceFactor = getBalanceFactor(node);
//因为平衡因数的绝对值不可能大于一,一旦大于一直接返回false
if (Math.abs(balanceFactor) > 1)
return false;
//最后递归调用左子树和右子树分别进行判断直至全部遍历完成
return isBalanced(node.left) && isBalanced(node.right);
}
//私有函数能够的到当前的平衡因数这里规定左子树的高度-右子树的高度
private int getBalanceFactor(Node node) {
if (node == null) {
return 0;
}
return getHeight(node.left) - getHeight(node.right);
}
向
AVL
树中添加元素
基本逻辑
- 传入三个元素 root ,key ,value 先判断根节点是否为空是的话维护size 然后创建一个新节点。
- 当元素不为空的时候比较插入的位置是左子树还是右子树
- 当插入元素以后更新树的高度,可以利用上面的辅助函数
getHeight()
函数,得到根节点的高度。 - 计算平衡因子以便于维护AVL树的平衡
- 辅助函数LL、RR、LR、RL函数共同来维持加入一个元素后树的平衡性。
①当添加一个新的节点后构成LL(左子树的左孩子)则需要进行右旋转
②当添加一个新的节点后构成RR(右子树的右孩子)则需要进行左旋转
③当添加一个新的节点后构成LR(左子树的右孩子)则需要对左子树进行左旋转,然后整体右旋转
④当添加一个新的节点后构成RL(右子树的左孩子)则需要对右子树进行右旋转,然后整体左旋转
图片解析 上图主要介绍了三个旋转LL、RR、RL旋转LR旋转和RL旋转相反应该先对以左子树为根节点进行左转,然后再以根节点为中心进行右转可以的到;
右旋转
rightRotate(Node y)
的实现及其原理
private Node rightRotate(Node y){
//首先用x暂存节点y的左子树
Node x=y.left;
//其次用节点temp存储x的右子树
Node temp=x.right;
//接着让x的右指针指向y
x.rignt=y;
//最后将x原本的右子树挂到y的左子树上
y.left=temp;
//旋转完成后要更新height的值
//总体来说只有y和x的高度变了其余的高度正常
y.height=Math.max(getHeight(y.left),getHeight(y.right))+1;
x.height=Math.max(getHeight(x.left),getHeight(x.right))+1;
return x;
}
左旋转
leftRotate(Node y)
的实现及其原理和右子树正好相反
private Node leftRotate(Node y) {
Node x = y.right;
Node temp = x.left;
x.left = y;
y.right = temp;
//更新height的 值
y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
return x;
}
add函数的实现
主要功能:实现元素的增加;代码如下:
public void add(K key,V value){
root=add(root,key,value);
}
private Node add(Node node,K key,V value){
//最简单的情况 整棵树为null
if(node==null){
size++;
return new Node(key,value);
}
//如果key小于根节点 遍历加入左子树
if(key.compareTo(node.key)<0)
node.left=add(node.left,key,value);
//当key大于根节点在右子树上插入
else if(key.compareTo(node.key) > 0)
node.right=add(node.right,key,value);
//反之当两者相同时 替换value值
else
node.value=value;
//以上完成插入数据后开始调节平衡度
//首先先更新下树的高度
node.height=1+Math.max(getHeight(node.left),getHeight(node.right));
//接着得到树的高度后开始计算平衡因子
int balanceFactor=getBalanceFactor(node);
if(Math.abs(balanceFactor)>1){
System.out.println("unbalanced:"+balanceFactor);
}
//得到平衡因数后开始维护平衡性
//LL 因为balanceFactor左子树高度-右子树高度
//后一个参数确定了该树就是左左类型的树直接调用右旋转
if (balanceFactor > 1 && getBalanceFactor(node.left) >= 0) {
//右旋转
return rightRotate(node);
}
//RR &&后的参数确保了该树是一个RR树
if (balanceFactor < -1 && getBalanceFactor(node.right) <= 0) {
return leftRotate(node);
}
//LR
if (balanceFactor > 1 && getBalanceFactor(node.left) < 0) {
//先以左孩子为节点进行左旋转
node.left = leftRotate(node.left);
//其次在以node为根节点进行右旋正好构建成功
return rightRotate(node);
}
//RL
if (balanceFactor < -1 && getBalanceFactor(node.right) > 0) {
node.right = rightRotate(node.right);
return leftRotate(node);
}
return node;
}
源代码
package AVL_;
import java.util.ArrayList;
@SuppressWarnings({"all"})
public class AVLTree<K extends Comparable<K>, V> {
private class Node {
public K key;
public V value;
public Node left, right;
public int height;
public Node(K key, V value) {
this.key = key;
this.value = value;
left = null;
right = null;
height = 1;
}
}
private Node root;
private int size;
public AVLTree() {
root = null;
size = 0;
}
public boolean isBST() {
ArrayList<K> keys = new ArrayList<>();
inOrder(root, keys);
for (int i = 0; i < keys.size(); i++) {
if (keys.get(i - 1).compareTo(keys.get(i)) > 0)
return false;
}
return true;
}
private void inOrder(Node node, ArrayList<K> keys) {
if (node == null) {
return;
}
inOrder(node.left, keys);
keys.add(node.key);
inOrder(node.right, keys);
}
//判断是否是一颗二叉平衡树
public boolean isBalanced() {
return isBalanced(root);
}
private boolean isBalanced(Node node) {
if (node == null)
return true;
int balanceFactor = getBalanceFactor(node);
if (Math.abs(balanceFactor) > 1)
return false;
return isBalanced(node.left) && isBalanced(node.right);
}
public int getSize() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
private int getHeight(Node node) {
if (node == null) {
return 0;
}
return node.height;
}
private int getBalanceFactor(Node node) {
if (node == null) {
return 0;
}
return getHeight(node.left) - getHeight(node.right);
}
public void add(K key, V value) {
root = add(root, key, value);
}
private Node add(Node node, K key, V value) {
if (node == null) {
size++;
return new Node(key, value);
}
if (key.compareTo(node.key) < 0) {
node.left = add(node.left, key, value);
} else if (key.compareTo(node.key) > 0) {
node.right = add(node.right, key, value);
} else {
node.value = value;
}
//更新height
node.height = 1 + Math.max(getHeight(node.right), getHeight(node.left));
//计算平衡因子
int balanceFactor = getBalanceFactor(node);
if (Math.abs(balanceFactor) > 1) {
System.out.println("unbalanced:" + balanceFactor);
}
//维护平衡性
//LL
if (balanceFactor > 1 && getBalanceFactor(node.left) >= 0) {
//右旋转
return rightRotate(node);
}
//RR
if (balanceFactor < -1 && getBalanceFactor(node.right) <= 0) {
return leftRotate(node);
}
//LR
if (balanceFactor > 1 && getBalanceFactor(node.left) < 0) {
node.left = leftRotate(node.left);
return rightRotate(node);
}
//RL
if (balanceFactor < -1 && getBalanceFactor(node.right) > 0) {
node.right = rightRotate(node.right);
return leftRotate(node);
}
return node;
}
//右旋转
private Node rightRotate(Node y) {
Node x = y.left;
Node temp = x.right;
x.right = y;
y.left = temp;
//更新height值
y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
return x;
}
//左旋转
private Node leftRotate(Node y) {
Node x = y.right;
Node temp = x.left;
x.left = y;
y.right = temp;
//更新height的 值
y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
return x;
}
private Node getNode(Node node, K key) {
if (node == null) {
return null;
}
if (key.compareTo(node.key) == 0) {
return node;
} else if (key.compareTo(node.key) < 0) {
return getNode(node.left, key);
} else
return getNode(node.right, key);
}
public V remove(K key) {
Node node = getNode(root, key);
if (node != null) {
root = remove(root, key);
return node.value;
}
return null;
}
//删除掉以node为根的二分搜索树中键为key的节点,递归算法
//返回删除节点后新的二分搜索树的根
private Node remove(Node node, K key) {
if (node == null)
return null;
Node retNode;
if (key.compareTo(node.key) < 0) {
node.left = remove(node.left, key);
retNode = node;
} else if (key.compareTo(node.key) > 0) {
node.right = remove(node.right, key);
retNode = node;
} else {
//待删除的左子树为空
if (node.left == null) {
Node rightNode = node.right;
node.right = null;
size--;
retNode = rightNode;
}
//待删除的右子树为空
else if (node.right == null) {
Node leftNode = node.left;
node.left = null;
size--;
retNode = leftNode;
} else {
//待删除左右子树都不为空
Node successor = minimum(node.right);
successor.right = remove(node.right, successor.key);
successor.left = node.left;
node.left = node.right = null;
retNode = successor;
}
if (retNode == null) {
return null;
}
//看是否需要维护平衡
//计算平衡因子
//更新height
retNode.height = 1 + Math.max(getHeight(retNode.right), getHeight(retNode.left));
//计算平衡因子
int balanceFactor = getBalanceFactor(retNode);
if (Math.abs(balanceFactor) > 1) {
System.out.println("unbalanced:" + balanceFactor);
}
//维护平衡性
//LL
if (balanceFactor > 1 && getBalanceFactor(retNode.left) >= 0) {
//右旋转
return rightRotate(retNode);
}
//RR
if (balanceFactor < -1 && getBalanceFactor(retNode.right) <= 0) {
return leftRotate(retNode);
}
//LR
if (balanceFactor > 1 && getBalanceFactor(retNode.left) < 0) {
retNode.left = leftRotate(retNode.left);
return rightRotate(retNode);
}
//RL
if (balanceFactor < -1 && getBalanceFactor(retNode.right) > 0) {
retNode.right = rightRotate(retNode.right);
return leftRotate(retNode);
}
}
return retNode;
}
private Node minimum(Node node) {
if (node.left == null) {
return node;
}
return minimum(node.left);
}
public boolean contains(K key) {
return getNode(root, key) != null;
}
public V get(K key) {
Node node = getNode(root, key);
return node == null ? null : node.value;
}
public void set(K key, V newValue) {
Node node = getNode(root, key);
if (node == null) {
throw new IllegalArgumentException(key + "doesn't exist!");
}
node.value = newValue;
}
}
2-3树
引导
学习红黑树之前我们先来了解2-3树。顾名思义一个节点位置有一个或者两个元素。2-3树的基本性质如下:
①满足二分搜索树的基本性质
②节点可以存放一个元素或者两个元素
③每个节点有两个孩子或者三个孩子
④两个孩子叫做2节点 三个孩子叫做三节点
⑤2-3树是一棵绝对平衡的树
⑥红黑树和2-3树等价&&所有的红色节点都是向左倾斜(自定义)
下面我们给出一个2-3树的图像:
由于红黑树的添加删除操作较为复杂这里值写出2-3树的添加操作如下图(和红黑树完全相同):
每次加入元素后都能保持数的绝对平衡。
红黑树的实现
实现准备
- 首先定义红黑色常量值
- 初始值均为红色
- 构建一个判断是否为红色节点的函数
- 构建左旋转
leftRotate()
右旋转rightRotate()
和颜色翻转函数flipColors()
函数 - 跟据以上准备实现红黑树
原理分析
结论
1.每个节点要么是红色要么是黑色
2.根节点是黑色的
3.每一个叶子节点(最后的空节点)是黑色的
4.如果一个节点是红色的 那么他的孩子节点都是黑色的,黑色节点的右孩子一定是黑色的
5.从任意一个节点到叶子节点,经过的黑色节点是一样的
红黑树的性能分析:最大高度2*logn 时间复杂度O(logn)
总体和AVL树一样不过多了一个颜色的判断;
private static final boolean BLACK = false;
private static final boolean RED = true;
//在Node节点中加入一个color共有属性 布尔类型的值
//并在Node的构造函数中使颜色初始值为RED
完成实现准备的代码
颜色翻转代码:
//颜色翻转
private void flipColors(Node node) {
node.color = RED;
node.left.color = BLACK;
node.right.color = BLACK;
}
添加新的元素代码:
//像红黑树中添加新的元素
public void add(K key, V value) {
root = add(root, key, value);
root.color = BLACK;
}
判断是否为红色:
private boolean isRed(Node node) {
//主要规避null叶子节点
if (node == null)
return BLACK;
return node.color;
}
如果红黑树的添加节点后该节点右子树为红色并且其左子树不为红色则需要进行左反转:
private Node leftRotate(Node node) {
//左旋转
Node x = node.right;
node.right = x.left;
x.left = node;
//当翻转完成后 需要对颜色进行调换
x.color = node.color;
node.color = RED;
return x;
}
如果红黑树的添加节点后该节点左子树为红色并且其左子树的左孩子为红色则需要进行右反转:
private Node rightRotate(Node node) {
//右旋转
Node x = node.left;
node.left = x.right;
x.right = node;
//颜色调换
x.color = node.color;
node.color = RED;
return x;
}
当添加元素的左孩子和右孩子均为红色的时候需要进行颜色的翻转最终实现的add函数为
//向以node为根的红黑树中插入新的元素
private Node add(Node node, K key, V value) {
if (node == null) {
size++;
return new Node(key, value); //默认插入一个红色的节点
}
if (key.compareTo(node.key) < 0) {
node.left = add(node.left, key, value);
} else if (key.compareTo(node.key) > 0) {
node.right = add(node.right, key, value);
} else {
node.value = value;
}
//红黑树的维护过程
if (isRed(node.right) && !isRed(node.left)) {
node = leftRotate(node);
}
if (isRed(node.left) && isRed(node.left.left))
node = rightRotate(node);
if (isRed(node.left) && isRed(node.right))
flipColors(node);
return node;
}
红黑树的总结
1.对于完全随机的数据,普通的二分搜索树很好用 缺点:极端情况下退化成链表(高度不平衡)
2.对于查询较多的情况下 AVL很好用
3.红黑树牺牲了平衡性(2logn的高度)
4.统计性能更优(常用于增删改查的所有操作)
红色的节点
}
if (key.compareTo(node.key) < 0) {
node.left = add(node.left, key, value);
} else if (key.compareTo(node.key) > 0) {
node.right = add(node.right, key, value);
} else {
node.value = value;
}
//红黑树的维护过程
if (isRed(node.right) && !isRed(node.left)) {
node = leftRotate(node);
}
if (isRed(node.left) && isRed(node.left.left))
node = rightRotate(node);
if (isRed(node.left) && isRed(node.right))
flipColors(node);
return node;
}
### 红黑树的总结
1.对于完全随机的数据,普通的二分搜索树很好用 缺点:极端情况下退化成链表(高度不平衡)
2.对于查询较多的情况下 AVL很好用
3.红黑树牺牲了平衡性(2logn的高度)
4.统计性能更优(常用于增删改查的所有操作)