AVLTree 学习

4 篇文章 0 订阅
4 篇文章 0 订阅
本篇博客主要讲解 AVLTree 的插入 修改 查找 判断
感谢观看,欢迎提出建议和问题
联系方式:blbagony@163.com
完整代码

AVLTree

维基百科是这么解释的

  • 在計算機科學中,AVL 樹是一種自平衡二叉搜索樹。這是第一個要發明的數據結構。在 AVL 樹中,任何節點的兩個子樹的高度最多不同一個; 如果在任何時候它們不同於一個,則重新平衡來恢復此屬性。查找,插入和刪除都在平均和最差情況下都採用O(log n)時間,其中 n 是操作之前樹中的節點數。插入和刪除可能需要通過一個或多個樹輪重新平衡樹。

  • AVL 樹以其兩位蘇聯發明家 Georgy Adelson-Velsky 和 Evgenii Landis 命名,他們在1962年的“信息組織算法”中發表。

  • AVL 樹通常與紅黑樹進行比較,因為它們都支持相同的操作集,並只需要O(log n)的时间复杂度用於基本操作。對於查找密集型應用程序,AVL 樹比紅黑樹更快,因為它們更加嚴格平衡。與紅黑樹相似,AVL 樹高度更加平衡。兩者都是在一般情況下,既不重量平衡也不 μ- 均衡任何 μ≤ 1 / 2 ; 也就是說,左右子树结点个数不同,但高度不会相差过大。


平衡係數

center

  • 在二叉樹中,將節點 N 的平衡因子定義為高度差
    BalanceFactor(N):= Height(RightSubtree( N )) - Height(LeftSubtree( N ))

  • 即右孩子的高度减去左孩子的高度。如果高度差的绝对值不大于一,則二叉樹被定義為 AVL 樹。
    BalanceFactor( N )∈{-1,0,+ 1 }

  • 一個節點的 BalanceFactor( N )< 0 被稱為“左重”,一個节点 BalanceFactor( N )> 0 稱為“右重”,一個节点 BalanceFactor( N )= 0 有時簡稱為近似平衡“ 。


节点插入
  1. 將元素插入到AVL樹中時,最初按照与插入二叉搜索樹的過程相似。更明確地說:如果前面的搜索沒有成功,先判断树是否为空,若为空,给 root 插入新節點。或者,如果樹不是空的,則搜索例程返回一個節點和方向(左或右),当返回的節點不具有子節點。判断给当前结点左边还是右边插入,返回的位置即为將要插入的節點的位置。

  2. 在插入之後,有必要檢查每個節點的祖先與 AVL 樹的平衡因子的一致性:這被稱為“回溯”。這是通過考慮每個節點的平衡因子來實現的。
    由於單插入,AVL子樹的高度不能增加一個以上,插入後的節點的臨時平衡因子將在[-2,+ 2] 的範圍內。需要檢查之前的每個節點,如果当前父节点的平衡因子保持在-1到+1的范围内,則只需要更新平衡因子,而不需要旋轉。然而,如果当前父节点平衡因子變得小于 -1 或大于 +1,則根據該節點的子樹是 AVL 不平衡的,並且需要各种旋轉。

    bool insert(const k& key, const v& value)
    {
        if (!_root){
            _root = new Node(key, value);
            return true;
        }
        Node* cur = _root;
        Node* parent = NULL;
        while (cur){
            parent = cur;
            if (key > cur->_key)
                cur = cur->_right;
            else if (key < cur->_key)
                cur = cur->_left;
            else
                return false;
        }
        cur = new Node(key, value);
        cur->_parent = parent;
        if (key > parent->_key)
            parent->_right = cur;
        else
            parent->_left = cur;
    /* 寻找合适的插入位置 */
        while (cur){
            parent = cur;
            if (key > cur->_key)
                cur = cur->_right;
            else if (key < cur->_key)
                cur = cur->_left;
            else
                return false;
        }
        cur = new Node(key, value);
        cur->_parent = parent;
        if (key > parent->_key)
            parent->_right = cur;
        else
            parent->_left = cur;

        /* 更新平衡因子 */
        while (parent)
        {
        /* 注意只能用指针判断,如果用 KEY 值判断,当左右旋转或右左旋转时,会修改原来更新好的平衡因子 */
            if (parent->_left == cur)
                parent->_bf--;
            else if (parent->_right == cur)
                parent->_bf++;
            if (parent->_bf == 0)
                break;
            else if (1 == abs(parent->_bf)){
                cur = parent;
                parent = parent->_parent;
            }
            else /* |平衡因子| = 2*/
            {
                if (2 == parent->_bf){
                    Node* subR = parent->_right;
                    if (1 == subR->_bf) /* 左旋 */
                        _left_left_rotation(parent);
                    else if (-1 == subR->_bf) /*先右旋再左旋*/
                        _right_left_rotation(parent);
                }
                else if (-2 == parent->_bf)
                {
                    Node* subL = parent->_left;
                    if (-1 == subL->_bf)
                        _right_right_rotation(parent); /* 右旋 */
                    else if (1 == subL->_bf)
                        _left_right_rotation(parent);  /*先左旋再右旋*/
                }
                /* 防御式编程,代码不会执行到这里 */
                else
                    return false;
            }
        }
    }

单旋

  • 这里主要讲左旋,因为左单璇和有单选类似

center
- 当平衡因子变成 2 时并且右孩子的子的平衡因子是 1,将父节点 x 移至右孩子(subR) z 的左边,因为有孩子的左边可能不为空,移动之前需要先保存右孩子的左边(subRL)t23,将 t23 移动至 x 的右边,因为即使是空也应该将 t23 移至x 的左边。判断 x 是其父节点 (grandfather)的左/右同时,判断父节点是否为空,若为空直接将 subR 交给 root, 不为空,改变 grandfather 的左或右指向 z (subR),判断 subRL 是否为空。不为空,更新其父亲指针指向 x,最后更新平衡因子。

    void _left_left_rotation(Node* parent)
    {
        Node* subR = parent->_right;
        Node* subRL = subR->_left;
        Node* grandfather = parent->_parent;
        parent->_parent = subR;
        subR->_left = parent;
        if (!grandfather){
            _root = subR;
            subR->_parent = NULL;
        }
        else {
            if (parent->_key > grandfather->_key)
                grandfather->_right = subR;
            else
                grandfather->_left = subR;
            subR->_parent = grandfather;
        }
        parent->_right = subRL;
        if (subRL)
            subRL->_parent = parent;
        parent->_bf = subR->_bf = 0;
    }

双旋

  • 这里主要将右左双选

center

  • 当平衡因子变为 2,并且 subR 的平衡因子为 -1 时。在这种情况下,改变平衡因子需要考虑三种可能。

    1. 当 subRL 的平衡因子是 -1 时,表示 subRL 当前左树重,旋转时将 subRL 的左树交给 parent 的右边,此时 parent 的平衡因子为 0;再看 subR,此时 subR 的右树重,更新其平衡因子为 1;

    2. 当 subRL 的平衡因子是 1 时,表示 subRL 当前右树重,旋转时将 subRL 的右树交给 subR 的左边,此时 subR 的平衡因子为 0;再看 parent,此时 parent 的左树重,更新器平衡因子为 -1;

    3. 当 subRL 的平衡因子为 0 时,表示只需要将 subRL 提升至父节点,parent subR, subRL 平衡因子都为 0;

    void _right_left_rotation(Node*& parent)
    {
        Node* subR = parent->_right;
        Node* subRL = subR->_left;
        int bf = subRL->_bf;
        _right_right_rotation(subR);
        _left_left_rotation(parent);
        if (1 == bf){
            parent->_bf = -1;
            subRL->_bf = 0;
        }
        else if (-1 == bf){
            parent->_bf = 0;
            subR->_bf = 1;
        }
        else
        {
            subR->_bf = parent->_bf = 0;
        }
        subRL->_bf = 0;
    }

判断是否为平衡树
  • 判断是否为平衡树,我们需要比较其左树与右树的高度差,若高度差在 ( - 2, + 2) 这个区间内,则我们可以说这棵树是一颗平衡树,否则不是。

  • 要求高度用递归方法很简便,返回条件为当前节点是否为空,不为空继续向左右子树找最深的那颗,若为空返回 0;

    int _getDeep(Node* node){
        if (!node) return 0;
        int left = _getDeep(node->_left);
        int right = _getDeep(node->_right);
        /* +1 因为自己也算一层 */
        return left > right ? left + 1: right + 1;
    }
  • 所以我们发现判断 AVL 树的代码就很好写了,只需要判断两颗字数高度差的绝对值不大于 1 就返回真,else 返回假。
    bool _isBalance(Node* node){
        if (!node) return true;
        int left = getDeep(node->_left);
        int right = getDoeep(node->_right);
        if ( abs(left - right) < 2 && _isBalance(node->_left) && _isBalance(node->_right))
            return true;
        return false;
    }

这里我们很容易发现一个问题,那就是时间复杂度和空间复杂度。每一次判断都需要将左子树和右子树的高度求一遍,时间复杂度为 o(N²),这种方法无疑增加了许多重复工作,素以我们需要让时间复杂度降低到 o(N)。

  • o(N)的方法判断是否为 AVL 树

  • 我们发现在计算高度时,计算机做了大量的重复工作。所以在求子节点高度时,我们可以让子节点把他的高度事先保存下来到其节点的信息中,但这样就需要修改节点的结构,这是一种侵入式编程,只是因为需要判断是否为 AVL 树而去修改子节点结构是不划算的,侵入式编程往往是不好的。这里我们可以在判断是否为平衡树时,先判断左右子树是否平衡,这时我们可以传入一个输出型参数,该参数保存子树高度。这样我们就不需要重复求子树高度,只需要遍历一遍就可以判断。将时间复杂度降为o(N)。

bool _isBalance(Node* node, int& deep){
    if (!node){
        deep = 0;
        return true;
    }
    int left = 0;
    if ( !_isBalance(node->_left, left) )
        return false;
    if ( !_isBalance(node->_right, right) )
        return false;
    deep = left > right ? left + 1 : right + 1;
    if ( abs(left - right) < 2)
        return true;
    return false;
}

后面还需要进行删除

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值