平衡二叉树之AVL树(一篇文章帮助你完全弄懂AVL树)

首先呢,在开头这里我给出我自己写的数据结构,里面包含了常见的数据结构,可以给大家提供参考

https://gitee.com/he-jerry/data-structure/tree/master/DataStructure

注意了呀!!! 我这篇文章里面的代码片段如果你直接ctrl + c + ctrl + v肯定跑不了,如果想要原码还是去上面链接的gitee仓库里面把AVL.h里面的代码考下来才是正确的

AVL树简介

二叉搜索树虽可以缩短查找的效率,但 如果数据有序或接近有序二叉搜索树将退化为单支树,查
找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii
和E.M.Landis在1962年
发明了一种解决上述问题的方法: 当向二叉搜索树中插入新结点后,如果能保证每个结点的左右
子树高度之差的绝对值不超过 1( 需要对树中的结点进行调整 ),即可降低树的高度,从而减少平均
搜索长度。
d91aff1a3379493d84f4680042fa5f3a.png

AVL的原理

AVL想要保证的是左子树的高度和右子树的高度之间相差不超过1,所以说对于每个节点来说我们需要存储一个bf(balance factor)的值来记录对于以当前节点为根的AVL的平衡情况

AVL树的节点设计

template <class K,class V>
    struct  AVLTreeNode {
        int _bf = 0;//balance factor 平衡因子
        pair<K,V> _kv;
        AVLTreeNode<K,V>* _parent;
        AVLTreeNode<K,V>* _left;
        AVLTreeNode<K,V>* _right;


        explicit AVLTreeNode(const pair<K,V> kv\
,AVLTreeNode<K,V>* parent = nullptr,AVLTreeNode<K,V>* left = nullptr,\
            AVLTreeNode<K,V>* right = \nullptr):_parent(parent),_left(left),_right(right),_kv(kv){}

    };

这里为什么要用三叉链的原因是因为对于平衡二叉树而言,经常涉及到节点之间的移动,所以说维护一个三叉链的结构能够帮助我们更好的处理问题

我们需要明确的一个点是:三叉链也好,balancefactor的引入也好,这些结构很好,毋庸置疑,但是,任何的良好的结构我们都是需要付出代价的,虽然三叉链的结构和balancefactor让我建树变得更加简单,但是维护三叉链和balancefactor是一件很麻烦的事 , 这里我想要表达的意思就是任何使我们方便事情都是需要付出代价的不要随意添加新的结构

AVL的添加新元素的操作

  1. 首先效仿搜索二叉树一样,找到我们需要添加的位置 , 如果没有就返回false
  2. 找到位置后,新建一个节点,将新的节点链到原来的树上面
  3. AVL在新增节点下可能出现不满足AVL的情况,这个时候我们就需要对树进行重构(翻转)

通过循环找到并添加新的节点,并处理他的父亲节点的balancefactor

enum PosfromAVLTree {
        AVLleft,
        AVLright,
    };
template <class K,class V,class Compare>
    bool AVLTree<K,V,Compare>::insert(const pair<K,V>& kv) {
        ::mycode::PosfromAVLTree position = ::mycode::PosfromAVLTree::AVLleft;
        Node* parent = nullptr;
        Node* cur = _root;
        if(cur == nullptr) {
            _root = new AVLTreeNode<K,V>(kv);
            _size++;
            return true;
        }
        while(cur) {
            if(_cmp(kv.first,cur->_kv.first)) {
                parent = cur;
                cur = cur->_left;
                position = ::mycode::PosfromAVLTree::AVLleft;
            }else if(_cmp(cur->_kv.first,kv.first)) {
                parent = cur;
                cur = cur->_right;
                position = ::mycode::PosfromAVLTree::AVLright;
            }
            else return false;
        }
        cur = new Node(kv);
        cur->_parent = parent;
        if(position == ::mycode::PosfromAVLTree::AVLleft) parent->_left\
    = cur;
        else parent->_right = cur;
        _size++;//插入成功后考虑平衡的问题

AVL添加新元素的时候进行重构操作

首先我们要探讨的问题是什么时候我们需要进行处理

  1. balancefactor从-1 -> 0 或者 1 -> 0这种情况是不需要我们进行操作的
  2. balancefactor从0->1 或者 0->-1这种时候我们对于当前的节点来说我们也是不用操作的,但是他的父亲节点还需要进行处理,就是这种情况影响的其实是他的父亲(祖辈节点)
  3. balancefactor变成-2或者2就是我们我们需要处理的时候了

AVL的重构是一个循环的过程从新增节点的父亲开始,他的所有祖辈可能出现问题,

这里我总结一下细节,一般我们我们旋转后,就就说明结束了,不用继续整理祖辈,只有上面的第二种情况我们需要继续处理祖辈,其他的情况下我们都可以直接退出了,因为祖辈已经没有问题了

右边过重,准确来说是右边的右边过重

ad81cd48b025428bbf2637a9f3256b27.png

这种事后我们应该进行左旋操作,旋转之后我们还需要对balancefactor进行更新

我们把根节点 记作root , 右边那个孩子节点记作rightChild,我们左旋的原理就是将rightChild作为新的root,然后root成为rightChild 的左孩子节点

65feea4081fe408fa337024c052f8342.png

基本上就是这样,然后我们需要重新更新root和rightChild节点的balancefactor

右边的左边过重

bbc41510b5b44915b4bfe03e288cfda1.png

和前面相比就是,右边重了,但是这里是右边的左边重了,前面那个是右边的右边重了

直接给出解决方式

daa95042ddaf469285bc481b7b88203e.png

上面这个就是传说中吹得很凶的先右旋再左旋的操作,其实他的目的就是把rightChild的左孩子推到root的位置而已然后root和rightChild成为左右护法,其实就这么简单,

相对应的后面还有左边重了(左边的左边重了和左边的右边重了)

AVL树的insert就这四种情况,是不是还是挺简单呢

所以现在我们直接看insert的全部的代码

template <class K,class V,class Compare>
    bool AVLTree<K,V,Compare>::insert(const pair<K,V>& kv) {
        ::mycode::PosfromAVLTree position = ::mycode::PosfromAVLTree::AVLleft;
        Node* parent = nullptr;
        Node* cur = _root;
        if(cur == nullptr) {
            _root = new AVLTreeNode<K,V>(kv);
            _size++;
            return true;
        }
        while(cur) {
            if(_cmp(kv.first,cur->_kv.first)) {
                parent = cur;
                cur = cur->_left;
                position = ::mycode::PosfromAVLTree::AVLleft;
            }else if(_cmp(cur->_kv.first,kv.first)) {
                parent = cur;
                cur = cur->_right;
                position = ::mycode::PosfromAVLTree::AVLright;
            }
            else return false;
        }
        cur = new Node(kv);
        cur->_parent = parent;
        if(position == ::mycode::PosfromAVLTree::AVLleft) parent->_left\
    = cur;
        else parent->_right = cur;
        _size++;//插入成功后考虑平衡的问题
        //更新平衡因子
        while(parent) {
            if(cur == parent->_left) { --parent->_bf; }
            else { ++parent->_bf; }
            if(parent->_bf == 0) break;
            else if(parent->_bf == -1 || parent->_bf == 1) {
                cur = parent;
                parent = parent->_parent;
            }else if(parent->_bf == -2 || parent->_bf == 2) {
                //进行旋转
                //分为左旋 + 右旋 + 先左旋后右旋 + 先右旋后左旋
                insertRotateAll(parent);
                break;
            }else assert(false);//断言结束
        }

        return true;
    }
    template<class K, class V, class Compare>
    void AVLTree<K, V, Compare>::RotateLeft(Node *parent) {
        //左旋的原理
        //由于某个根节点的平衡因子本身处于1的状态,这个时候如果我们在右子树进行push的时候
        //平衡被打破,进行右旋,将rightChild的left作为parent的rightchild并且将parent作为
        //rightChild的左节点同时将rightchild作为新的根节点
        //
        //右旋我们要考虑几个关键的点
        //1.设置的时候不要忘了我们这里是三叉链的结构
        //2.rCLeftChild可能是nullptr
        //3.旋转之后,parent 和 rightChild 的 balancefactor都应该变成0
        Node* tmp = parent->_parent;//记录根节点
        Node* rightChild = parent->_right;
        Node* rCLeftChild = rightChild->_left;
        parent->_right = rCLeftChild;
        rightChild->_left = parent;
        parent->_parent = rightChild;
        if(rCLeftChild) rCLeftChild->_parent = parent;//由于rCleftChild可能为空
        rightChild->_parent = tmp;//他的parent是parent的parent
        if(tmp) {
            if(parent == tmp->_left) tmp->_left = rightChild;
            else tmp->_right = rightChild;
        }else _root = rightChild;//这个位置也很重要,更新root节点别忘了
        parent->_bf = rightChild->_bf = 0;
    }
    template<class K, class V, class Compare>
    void AVLTree<K, V, Compare>::RotateRight(Node *parent) {
        Node* tmp = parent->_parent;
        Node* leftChild = parent->_left;
        Node* lCRightChild = leftChild->_right;
        parent->_left = lCRightChild;
        leftChild->_right = parent;
        parent->_parent = leftChild;
        if(lCRightChild) lCRightChild->_parent = parent;
        leftChild->_parent = tmp;
        if(tmp) {
            if(parent == tmp->_left) tmp->_left = leftChild;
            else tmp->_right = leftChild;
        }else _root = leftChild;
        parent->_bf = leftChild->_bf = 0;
    }
    template <class K,class V,class Compare>
    void AVLTree<K,V,Compare>::clear(Node* root) {
        if(root == nullptr) return;
        clear(root->_left);
        clear(root->_right);
        delete root;
        _size = 0;
    }
    template <class K,class V,class Compare>
    void AVLTree<K,V,Compare>::insertRotateAll(Node* parent) {
        if(parent->_bf == 2 && parent->_right->_bf == 1)
            RotateLeft(parent);//左单旋
        else if(parent->_bf == -2 && parent->_left->_bf == -1)
            RotateRight(parent);
        else if(parent->_bf == 2 && parent->_right->_bf == -1) {
            int bfOfRCChild = parent->_right->_left->_bf;
            RotateRight(parent->_right);
            RotateLeft(parent);
            //这里唯一的问题就是旋转之后处理balancefactor的问题
            parent = parent->_parent;//parent在这里发生了变化
            if(bfOfRCChild == 1) {
                parent->_left->_bf = -1;
            } else if(bfOfRCChild == -1)
                parent->_right->_bf = 1;
            else if(bfOfRCChild == 0) assert(true);
            else assert(false);
        }
        else if(parent->_bf == -2 && parent->_left->_bf == 1) {
            int bfOfLCChild = parent->_left->_right->_bf;
            RotateLeft(parent->_left);
            RotateRight(parent);
            parent = parent->_parent;
            if(bfOfLCChild == 0) assert(true);
            else if(bfOfLCChild == 1) parent->_left->_bf = -1;
            else if(bfOfLCChild == -1) parent->_right->_bf = 1;
            else assert(false);
        }

    }

AVL树的删除新节点的操作

1.找到节点 , 当然既然你已经来看AVL树,当然找到这个节点当然很简单,所以这里我就不过多的赘述了(偷个懒)

这里我还是简单的说一下吧,因为删除有很多种方式,但是为了方便我们后续的整理,我们一般采用的删除方式是,用两个cur 和 parent指针来进行整理

1.删除节点是叶子节点,cur = nullptr , parent 就是他的父亲 ,然后while(parent)进行整理

2.删除的节点有一个孩子,把他的孩子链到parent上,cur = 这个孩子节点 ,然后while(parent)进行整理

3.删除的节点有两个孩子      

        经典操作,找到后继节点:这里你可能会疑问什么是后继节点,后继节点就是比这个大的第一个节点,怎么找到呢(写个伪代码)

        

//cur 是我们需要删除的节点
Node* rightMin = cur->_right;
while(rightMin->_left)
    rightMin = rightMin->_left;
//这里的rightMin就是后继结点

是不是还是挺好理解的,找到之后,我们将cur的值替换成rightMin的值,这样的话就变成删除rightMin这个只有一个或者没有孩子的节点,是不是很巧妙

删除是删除完了,这里我们又到了需要我们整理重构这个AVL树的过程了,当然,还是只有四种情况(或者说只有两种情况)

b2884a83eefd4f7a8267ef0bf7e4411e.png

你以为这里就结束了,哈哈哈,当然不是,如果如rightChild的两个孩子高度都是n 或者 右边那个更高,这样的确就结束了,但是如果rightChild->_bf == -1也就是说他的左孩子更高,就惨了,这个时候我们就清楚我们的先右旋再左旋,说白了就是让rightChild->_left去当root,当大哥,rightChild和root当他的左右护法

总结

到这里位置,我们AVL树"最困难的insert和erase"都解决了,其实也没有那么难是吧,这里我并没有实现iterator,有兴趣的想伙伴可以实现一下iterator,还是挺简单的,可以在gitee上私戳我,或者直接在我的博客下面留言,我都是会看到的,欢迎我们一起讨论,后面我还会出红黑树 , 哈希表的底层实现逻辑 , 由于现在实践中hashtable和RedBlackTree用的普遍更多,或者说如果你迫不及待的想知道底层实现原理,也可以去我博客开头的链接里面看hashTable.h和RBTree.h两个文件(还是挺简单的,肯定比你在标准库里面看代码好的多)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值