平衡二叉树平衡因子怎么计算_平衡二叉树(AVL Tree)旋转机制分析

本文详细介绍了AVL树的实现,包括节点类的设计、IMap接口的定义以及AVLTree类的实现。重点讲解了在添加和删除操作中如何维护AVL树的平衡,通过获取平衡因子和执行四种旋转(LL、RR、LR、RL)来确保树的高度平衡。同时,文章指出了AVL树旋转的一个常见误解,并提供了相应的示例图解。
摘要由CSDN通过智能技术生成

平衡二叉树(AVL Tree)

概述

AVL树是以二分搜索树(BST)为底层数据结构而实现的,其特性是需要维护AVL的|平衡因子| <= 1

平衡因子
对于一个父节点的左右子树高度差的绝对值需要 <= 1

AVL最复杂的操作在于add()remove()两个方法需要维护AVL的平衡二叉树特性,细节在下面展开

节点类

由于需要知道AVL一个节点的高度,因此需要在BST的内部类Node中新维护一个成员变量height

public class Node<K, V> {
    K key;
    V value;
    int height;
    Node<K, V> left, right;

    public Node(K key, V value) {
        this.key = key;
        this.value = value;
        this.left = null;
        this.right = null;
        this.height = 1;
    }
}

IMap接口

该接口定义了BST的方法

public interface IMap<K, V> {
    void add(K key, V value);

    V remove(K key);

    boolean contains(K key);

    V get(K key);

    void set(K key, V value);

    int getSize();

    boolean isEmpty();
}

AVLTree类

成员变量以及构造方法

private int size;
private Node<K, V> root;

public AVLTree() {
    root = null;
    size = 0;
}

基本的方法

下面的方法均是为了实现add()remove()方法时,可能造成AVL的平衡性被打破而封装的辅助方法

  • getHeight()
private int getHeight(Node<K, V> node) {
    if (node == null) {
        return 0;
    }
    return node.height;
}

该方法用于获取某节点的高度,且维护nodenull时可能抛出的异常

  • getNode(Node node, K key)
private Node<K, V> getNode(Node<K, V> node, K key) {
    if (root == 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);
    }
}

根据key获取节点

  • getBalanceFactor(Node node)
// 获取平衡因子
private int getBalanceFactor(Node<K, V> node) {
    if (node == null) {
        return 0;
    }
    return getHeight(node.left) - getHeight(node.right);
}

获取平衡因子,并且维护了node可能为null而抛出异常的情况

核心方法

  • add(Node node)
@Override
public void add(K key, V value) {
    root = add(root, key, value);
}

private Node<K, V> add(Node<K, V> 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 {
        // 如果插入节点存在,此处定义为改变 Value 的值(也可以根据情况不做改变)
        node.value = value;
    }

    node.height = Math.max(getHeight(node.left), getHeight(node.right) + 1);
    int balanceFactor = getBalanceFactor(node);

    // 平衡维护
    // 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.right) < 0) {
        node.left = leftRotate(node.left);
        return rightRotate(node);
    }
    // RL
    if (balanceFactor < -1 && getBalanceFactor(node.right) > 0) {
        node.right = leftRotate(node);
        return rightRotate(node.right);
    }

    return node;
}
  • remove(K key)
@Override
public V remove(K key) {
    Node<K, V> node = getNode(root, key);
    if (node != null) {
        root = remove(root, key);
        return node.value;
    }
    return null;
}

private Node<K, V> remove(Node<K, V> node, K key) {
    if (node == null) {
        return null;
    }


    Node<K, V> 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<K, V> rightNode = node.right;
            node.right = null;
            size--;
            retNode = node;
        }

        if (node.right == null) {
            Node<K, V> leftNode = node.left;
            node.left = null;
            size--;
            retNode = node;
        }

        // 左右子树均不为空的情况
        Node<K, V> minimum = minimum(node.right);

        minimum.right = removeMin(node.right);

        minimum.left = node.left;

        node.left = node.right = null;
        retNode = minimum;
    }

    retNode.height = 1 + Math.max(getHeight(retNode.left), getHeight(retNode.right));

    int balanceFactor = getBalanceFactor(retNode);

    // 平衡维护
    // 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.right) < 0) {
        retNode.left = leftRotate(retNode.left);
        return rightRotate(retNode);
    }
    // RL
    if (balanceFactor < -1 && getBalanceFactor(retNode.right) > 0) {
        retNode.right = leftRotate(retNode);
        return rightRotate(retNode.right);
    }

    return retNode;
}

维护AVL的平衡性质——旋转

这里是文章的核心内容,即如何保证AVL的平衡性质,这里开始详细描述维护AVL特性的机制——旋转

旋转分为四种形式,分别为:

  • LL旋转
  • RR旋转
  • LR旋转
  • RL旋转

只需要理解一种旋转机制,就可以理解其它的旋转机制

这里以LL和LR旋转为例

LL旋转

  • 如图所示,根节点的平衡因子为2,而破坏该AVL的节点在根节点的左子树左节点,因此需要LL旋转来维护其平衡

9551ab88e647dbda36f6c1a4449f6fd4.png
  • 接下来根据实际代码进一步理解这个机制
  • 为了结合代码,现在虚拟出三个节点(或子树),它们可以是null,也可以实际存在

02dea7e7a715afa47d5ebfc55382984f.png
  • 目前由于是LL旋转,那么提供一个private方法用于维护平衡,下面给出方法详细代码,代码后有每一个表达式的详细解释
// 右旋转
private Node<K, V> rightRotate(Node<K, V> y) {
    Node<K, V> x = y.left;
    Node<K, V> t3 = x.right;
    x.right = y;
    y.left = t3;

    // 更新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;
}
  • 第一行表达式Node<K, V> x = y.left,将发现节点y的左子树暂存

65675c6c4478fa4c77d0dfded147001a.png
  • 第二行表达式Node<K, V> t3 = x.right,将单独拿出的节点的右子树t3暂存

3dfc0ad05b24a931e12fe46e74eea4d2.png
  • 第三行表达式x.right = y,将发现节点y接入x的右子树

7744e3d0be941c2b0afd9b5a506533b8.png
  • 第四行表达式y.left = t3,将暂存节点t3插入发现节点y的左节点

70f90bdbc86cc67fcce0f14c552d0d54.png
  • 注意:以上的每个操作都是满足了二分搜索树(BST)的特性,这点对于理解旋转机制很重要
  • 由于最终平衡后的AVL高度发生了改变,因此需要对更新后的AVL高度进行维护
// 更新height
y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;

LR旋转

LR旋转和LL旋转的差别在于LR旋转需要进行两次旋转操作,具体步骤如下:

eeb8a71ffe3c2b3c4264ff9c16dc8c21.png
  • 首先对于上面的AVL进行一次左旋转,然后就会暂时将其转变为LL旋转的状态

4d18cbf36023c0d239a08c8a75d4598c.png
  • 然后再进行一次右旋转,即可恢复到AVL的状态

1306a5daceb1bc2c61a59fad04925ac5.png

易错点分析

AVL有一个非常容易误解的地方,如图

770e066b56ecd75bbe1dd4c60429d770.png

实际上旋转的命名(LL、RR、LR、RL)也都是根据破坏节点t1(t2)发现节点y的相对位置命名

b72fdaafd8cf6f84d8fe8806e306baa3.png

也就是说无论是:

0115cfdb7353e77d8da3cc0f7657c093.png

还是:

d1bb4bad66f2ae143238633d3d6961fd.png

旋转方式是相同的,均为LL旋转,结果如图:

0dce531978a24d8a4dbcd080d98d12b6.png
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值