手撕 – AVL树、红黑树

文章目录
1.AVL树
1.1AVL树的概念
二叉搜索树虽然可以缩短查找的效率,但是如果**数据有序或者接近有序将退化为单支树,查找元素相当于在顺序表中搜索元素,**效率低下。因此:当向二叉搜索树中插入新节点后,如果能保证每一个节点的左右子树高度只差的绝对值不超过1,即通过旋转降低树的高度,从而减少平均搜索长度
1.2AVL树的性质
- 左右子树都是AVL树
- 左右子树高度差(简称平衡因子)的绝对值不超过1
- AVL是一颗高度平衡的二叉平衡树,如果其有n个节点,其高度可以保持在
logn,搜索时间复杂度为O(logn)
1.3AVL树的实现 – Java代码
public class AVLTree {
//定义节点
static class TreeNode{
public int val;
public TreeNode left;
public TreeNode right;
public int bf;//平衡因子
public TreeNode parent;//父节点
public TreeNode(int val) {
this.val = val;
}
}
public TreeNode root;
public boolean insert(int val){
//先按照二叉搜索树的方式插入新节点
TreeNode node = new TreeNode(val);
if(root == null){
root = node;
return true;
}
TreeNode cur = root;
TreeNode parent = null;
while(cur != null){
if(cur.val > val){
parent = cur;
cur = cur.left;
} else if (cur.val == val) {
return false;//AVL树中不存在数值相同的节点,如果找到相等节点直接返回false
}else{
//cur.val < val
parent = cur;
cur = cur.right;
}
}
if(parent.val > val){
parent.left = node;
cur = parent.left;
}else{
parent.right = node;
cur = parent.right;
}
//不要忘了更新新节点的父亲节点
node.parent = parent;
//调整节点的平衡因子
while(parent != null){
//更新双亲节点的平衡因子
if(cur == parent.right){
parent.bf++;
} else if (cur == parent.left) {
parent.bf--;
}
//满足AVL树的性质,插入成功
if(parent.bf == 0){
break;
} else if (parent.bf == -1 || parent.bf == 1) {
//继续向上查找
cur = parent;
parent = parent.parent;
} else {
//平衡因子为2
if(parent.bf == 2){
if(cur.bf == -1){
rotateRL(parent);
}else{
//左旋
rotateL(parent);
}
}else if (parent.bf == -2){
//平衡因子为-2
if(cur.bf == -1){
//右旋
rotateR(parent);
}else{
rotateLR(parent);
}
}
break;
}
}
return true;
}
private void rotateRL(TreeNode parent) {
//记录
TreeNode subR = parent.right;
TreeNode subRL = subR.left;
int bf = subRL.bf;
rotateR(parent.right);
rotateL(parent);
if(bf == -1){
subRL.bf = 0;
subR.bf = 1;
parent.bf = 0;
}else if(bf == 1){
subR.bf = 0;
parent.bf = -1;
subRL.bf = 0;
} else if (bf == 0) {
subR.bf = 0;
parent.bf = 0;
subRL.bf = 0;
}
}
private void rotateLR(TreeNode parent) {
//记录
TreeNode subL = parent.left;
TreeNode subLR = subL.right;
int bf = subLR.bf;
rotateL(parent.left);
rotateR(parent);
if(bf == -1){
subLR.bf = 0;
subL.bf = 0;
parent.bf = 1;
}else if(bf == 1){
subL.bf = -1;
parent.bf = 0;
subLR.bf = 0;
} else if (bf == 0) {
subLR.bf = 0;
subL.bf = 0;
parent.bf = 0;
}
}
private void rotateR(TreeNode parent) {
TreeNode subL = parent.left;
TreeNode subLR = subL.right;
parent.left = subLR;
if(subLR != null){
subLR.parent = parent;
}
subL.right = parent;
TreeNode pParent = parent.parent;
parent.parent = subL;
if(root == parent){
root = subL;
root.parent = null;
}else{
if(parent == pParent.right){
pParent.right = subL;
}else{
pParent.left = subL;
}
subL.parent = pParent;
}
parent.bf = subL.bf = 0;
}
private void rotateL(TreeNode parent) {
TreeNode subR = parent.right;
TreeNode subRL = subR.left;
parent.right = subRL;
if(subRL != null){
subRL.parent = parent;
}
subR.left = parent;
TreeNode pParent = parent.parent;
parent.parent = subR;
if(parent == root){
root = subR;
root.parent = null;
}else{
if(parent == pParent.right){
pParent.right = subR;
}else{
pParent.left = subR;
}
subR.parent = pParent;
}
parent.bf = subR.bf = 0;
}
//AVL树的验证
public void inOrder(TreeNode root){
if(root == null){
return;
}
inOrder(root.left);
System.out.print(root.val + " ");
inOrder(root.right);
}
//求树的高度
public int height(TreeNode root){
if(root == null){
return 0;
}
int left = height(root.left);
int right = height(root.right);
return left > right ? left + 1 : right +1;
}
//判断树的高度差
public boolean isBalance(TreeNode root){
if(root == null){
return true;
}
int leftH = height(root.left);
int rightH = height(root.right);
//检查平衡因子是否有问题
if(rightH - leftH != root.bf){
System.out.println(root.val + "平衡因子异常");
return false;
}
return Math.abs(leftH-rightH)<=1 && isBalance(root.left) && isBalance(root.right);
}
}
注解:
当前节点的平衡因子:当前节点右子树高度 - 左子树高度
1.4AVL树的性能分析
- AVL可以保证高效的查找时间复杂度,为O(logN)
- 如果需要对于AVL树的结构做一些修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。
- 适用场景:适用于需要一种查询高效且有序的数据结构,而且数据的个数是静态的,可以考虑AVL树,但是一个结构需要经常修改,就不太合适
2.红黑树
2.1概念
红黑树,是一种二叉搜索树,但每个节点上增加一个存储位表示节点的颜色,可以是RED或者BLACK。通过对任何一条从根到叶子的路径上各个节点的着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的
2.2红黑树的性质
- 最长路径最多是最短路径的二倍
- 每一个节点不是红色就是黑色
- 根节点是黑色的
- 如果一个节点是红色的,则它的两个孩子节点是黑色的
没有两个连续的红色节点 - 对于每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含
相同数目的黑色节点 - 每一个叶子节点都是黑色的(此处叶子节点指的是空节点)

注意:一般情况下,一个正常的红黑树不会出现上述情况,可以用来验证性质1
2.3红黑树的实现
//定义一个枚举
public enum Color {
RED,BLACK
}
public class RBTree {
//红黑树节点
static class RBTreeNode{
public int val;
public RBTreeNode left;
public RBTreeNode right;
public RBTreeNode parent;
Color color;//节点默认为红色,若为黑色,容易产生不平衡因素--需要新增节点
public RBTreeNode(int val) {
this.val = val;
this.color = Color.RED;
}
}
public RBTreeNode root;
public boolean insert(int val){
RBTreeNode node = new RBTreeNode(val);
if(root == null){
root = node;
root.color = Color.BLACK;//注意将节点置为黑色,根节点
return true;
}
RBTreeNode cur = root;
RBTreeNode parent = null;
while(cur != null){
if(cur.val > val){
parent = cur;
cur = cur.left;
}else if(cur.val < val){
parent = cur;
cur = cur.right;
}else{
return false;//红黑树中不存在相同节点
}
}
if(parent.val > val){
parent.left = node;
node.parent = parent;//不要忘记确定parent的指向
} else if (parent.val < val) {
parent.right = node;
node.parent = parent;
}
cur = node;//cur指向新插入的节点
//插入节点默认为红色,主要是看插入节点的父节点是否为红色
while(parent != null && parent.color == Color.RED){
RBTreeNode grandFather = parent.parent;//若满足parent为红色,则节点一定存在
if(parent == grandFather.left){
RBTreeNode uncle = grandFather.right;
//情况1
if(uncle!= null && uncle.color == Color.RED){
parent.color = Color.BLACK;
uncle.color = Color.BLACK;
grandFather.color = Color.RED;
//判断parent是否为空,即grandparent是否就等于root
if(grandFather == root){
grandFather.color = Color.BLACK;//将根节点置为空
break;
}
//继续向上修改
cur = grandFather;
parent = cur.parent;
}else{
//情况3
if(parent.right == cur){
rotateL(parent);
//交换
RBTreeNode tmp = cur;
cur = parent;
parent = tmp;
}
//情况2
rotateR(grandFather);
parent.color = Color.BLACK;
grandFather.color = Color.RED;
}
}else{
//parent == grandFather.right
RBTreeNode uncle = grandFather.left;
if(uncle != null && uncle.color == Color.RED){
uncle.color = parent.color = Color.BLACK;
grandFather.color = Color.RED;
if(grandFather == root){
grandFather.color = Color.BLACK;
}
cur = grandFather;
parent = cur.parent;
}else{
//情况3
if (cur == parent.right){
rotateR(parent);
//交换
RBTreeNode tmp = cur;
cur = parent;
parent = tmp;
}
//情况2
rotateL(grandFather);
grandFather.color = Color.RED;
parent.color = Color.BLACK;
}
}
}
return true;
}
private void rotateR(RBTreeNode parent) {
RBTreeNode subL = parent.left;
RBTreeNode subLR = subL.right;
parent.left = subLR;
if(subLR != null){
subLR.parent = parent;
}
subL.right = parent;
RBTreeNode pParent = parent.parent;
parent.parent = subL;
if(root == parent){
root = subL;
root.parent = null;
}else{
if(parent == pParent.right){
pParent.right = subL;
}else{
pParent.left = subL;
}
subL.parent = pParent;
}
}
private void rotateL(RBTreeNode parent) {
RBTreeNode subR = parent.right;
RBTreeNode subRL = subR.left;
parent.right = subRL;
if(subRL != null){
subRL.parent = parent;
}
subR.left = parent;
RBTreeNode pParent = parent.parent;
parent.parent = subR;
if(parent == root){
root = subR;
root.parent = null;
}else{
if(parent == pParent.right){
pParent.right = subR;
}else{
pParent.left = subR;
}
subR.parent = pParent;
}
}
//中序遍历
public void inOrder(RBTreeNode root){
if(root == null){
return;
}
inOrder(root.left);
System.out.print(root.val + " ");
inOrder(root.right);
}
//检测其是否满足红黑树的性质
public boolean isValidRBTree(RBTreeNode root) {
//如果是空树,满足
if (root == null) {
return true;
}
//根节点不是黑色的,不是红黑树
if (root.color != Color.BLACK) {
System.out.println("违反了红黑树的性质:根节点不是黑色");
return false;
}
//获取单条路径中黑色节点的个数
RBTreeNode cur = root;
int blackCount = 0;
while (cur != null) {
if (cur.color == Color.BLACK) {
blackCount++;
}
cur = cur.left;
}
return _isValidRBtree(root, 0, blackCount);
}
public boolean _isValidRBtree(RBTreeNode root, int pathCount, int blackCount){
if(root == null){
return true;
}
//遇到黑色的,pathCount++
if(root.color == Color.BLACK){
pathCount++;
}
RBTreeNode parent = root.parent;
if(parent != null && parent.color == Color.RED && root.color == Color.RED){
System.out.println("有连在一起的红色节点");
return false;
}
if(root.left == null && root.right == null){
if(pathCount != blackCount){
System.out.println("路径中黑色节点数量不一致");
return false;
}
}
return _isValidRBtree(root.left, pathCount, blackCount) && _isValidRBtree(root.right, pathCount, blackCount);
}
}
2.4AVL树和红黑树的比较
| 树类型 | AVL树 | 红黑树 |
|---|---|---|
| 查找效率 | O(logN) | O(logN) |
| 特点 | 追求绝对平衡,插入、删除需要进行大量旋转 | 不追求绝对平衡,相对而言降低了插入和旋转的次数 |
| 在增删结构中性能比较 | 相对较低 | 更高的效率 |
2.5红黑树的应用
| 树类型 | AVL树 | 红黑树 |
|---|---|---|
| 查找效率 | O(logN) | O(logN) |
| 特点 | 追求绝对平衡,插入、删除需要进行大量旋转 | 不追求绝对平衡,相对而言降低了插入和旋转的次数 |
| 在增删结构中性能比较 | 相对较低 | 更高的效率 |
2.5红黑树的应用
Java集合框架中:TreeMap、TreeMap底层使用的就是红黑树

5万+

被折叠的 条评论
为什么被折叠?



