AVL树模拟(kv模型)

目录

一、相关概念

二、AVL树节点构造

三、AVL树的插入情况分析

1. 按照二叉搜索树的方式插入新节点

2. 调整节点的平衡因子

(1)更新原则

(2)新增节点对部分祖先节点的影响

四、旋转处理

(1)右单旋

1、处理思路

2、处理流程

3、平衡因子调整

(2)左单旋

(3)先左单旋再右单旋

1、处理方法:

2、平衡因子的控制

(4)先右单旋再左单旋

1、处理方法:

2、平衡因子的控制

总结

五、AVL树的验证

1、中序遍历

2、AVL平衡检查

3、调试技巧

六、AVL树的性能

七、AVL模拟代码

一、相关概念

        二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,插入有序数据时,查找元素相当于在顺序表中搜索元素,效率低下。于是建立了新的结构,当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

AVL树的底层仍然是搜索二叉树,不过是一颗高度平衡的二叉树;

搜索二叉树的单只情况查找效率低,近似为O(N)查找效率

二、AVL树节点构造

        Q:如何来控制搜索二叉树的平衡问题,是AVL的关键,我们向每个节点结构添加平衡因子 ,何为平衡因子呢?

        A:平衡因子=右子树高度-左子树高度

平衡因子并不是AVL所必须具有的,它只是我们来模拟AVL所借助的一种控制方式

        其次为了向上查找,还增加了parent的节点,能够找到当前节点的父亲节点,让节点之间更加灵活,便于我们做相关操作。下图为,模拟AVL树(kv模型)的节点结构

三、AVL树的插入情况分析

1. 按照二叉搜索树的方式插入新节点

(1)如果树中有此元素,插入失败

(2)若没有此元素,则找到应该插入的正确位置

注意

        循环找位置过程中,保留父亲节点,方便新节点与树的链接(即新节点与父亲节点的链接)

2. 调整节点的平衡因子

(1)更新原则

新增节点是父亲的左边,父亲的平衡因子加一,

新增节点是父亲的右边,父亲的平衡因子减一。

(2)新增节点对部分祖先节点的影响

        是否更新取决于parent的高度是否发生变化,是否影响到了爷爷节点,这个更新有可能持续到根节点,所以我们借助条件(parent不为空)来判断循环结束。

1、更新后parent的平衡因子为0,说明parent所在的子树高度不变,不会影响到爷爷节点的平衡因子

(更新前,parent的平衡因子为1或者-1,更新后左右均衡了,parent的高度不变,不会影响到爷爷节点)

2、更新后parent的平衡因子为1或者-1,parent所在的子树高度变了,会影响到爷爷节点,

(更新前parent的_bf是0,parent的一边插入后不均衡了),继续向上更新爷爷节点

3、更新后parent的_bf为2或者-2,parent违反了平衡规则,需要进行旋转处理

旋转之后,高度回到插入之前,不会影响到其他祖先,直接退出调整。

四、旋转处理

        如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化,降低当前树的高度。根据节点插入位置的不同,AVL树的旋转分为四种,覆盖所有场景:

        不平衡是插入某一个节点所导致的,导致根节点的平衡因子绝对值为2,或者-2,如果一棵树本来是平衡的,那么导致一树根不平衡,那么就只有四个节点位置,不同位置对应不同的解决办法:

1、根的左子树的左子树导致的不平衡(右单旋

2、根的左子树的右子树导致的不平衡(左右旋

3、根的右子树的左子树导致的不平衡(左单旋

4、根的右子树的右子树导致的不平衡(右左旋

可以理解为1,4情况是挂在子树的边缘。2,3情况是挂在子树的中间

平衡因子图形表示:

树中插入节点,如果某一棵子树平衡因子异常,在这颗子树中出现的问题都只会是上图四种情况之一。

介绍四种情况的对应解决办法之前,首先我们先记住几个标量

parent平衡因子异常的节点

subR :parent节点的右孩子

subRL :parent节点的右孩子的左孩子

subL :parent节点的左孩子

subLR :parent节点的左孩子的右孩子

(1)右单旋

1、处理思路

将subL的原来右节点subLR变为parent的左节点,将subL的新右节点变为parent节点

2、处理流程

(1)、处理parent与subLR新关系的链接(subLR不为空,其父节点指向parent)

(2)、处理parent与subL新关系的链接(保存旧parent的父亲节点与subL节点链接)

(3)、处理parent的父亲节点与subL节点链接(parent可能是根节点)

3、平衡因子调整

parent的平衡因子置0,被旋转到根位置的子节点的平衡因子置0(subL)

注意细节:

1、subLR可能是空,他没有父节点,不能其父节点指向parent

2、parent可能是根节点,旋转完成后,要更新根节点

3、注意链接旧的根位置节点的parent和根位置的新节点,非根节点:parent是parent父亲的左节点,那么parent父亲的左节点与subL节点链接,parent是parent父亲的右节点,那么parent父亲的右节点与subL节点链接

(2)左单旋

旋转思路以及方法同右单旋一样,两个情况是相似的,在图形表示上成对称情况。

(3)先左单旋再右单旋

Q:为什么不可以单旋解决?

A:单旋会将原来一边高的,变到另外一边高。所以这种情况下要进行左右旋。

1、处理方法:

1、先对subL进行左旋,将高度最大的子树全部挂到子树最边缘处

2、再对parent进行右旋

2、平衡因子的控制

        我们看图的最终结果可以知道,新节点插在subLR的左节点和右节点是不同的,因为subLR的左节点会分给subL,而右节点会分给parent,这导致,插入在subRL的不同位置,我们最终在调整平衡因子的时候会有不同。甚至在h=0的情况下,subLR就是一个新插节点,调整平衡因子的时候会有不同。所以分为三种情况来讨论平衡因子的调节。如何来判断是哪种情况,就要查看我们插入新节点后subRL的平衡因子来判断了。

(1)b插入(subRL==-1)

        parent平衡因子置1

        subL平衡因子置0

        subLR平衡因子放置0

(2)c插入(subRL==1)

        parent平衡因子置0

        subL平衡因子置-1

        subLR平衡因子放置0

(3)本身是新插节点(subRL==0)

        parent平衡因子置0

        subL平衡因子置0

        subLR平衡因子放置0

(4)先右单旋再左单旋

同理右左旋:原理及处理办法相似,在图形上表示为对称情况,不做相关分析

1、处理方法:

1、先对subR进行右旋,将高度最大的子树全部挂到子树最边缘处

2、再对parent进行左旋

2、平衡因子的控制

(1)b插入(subRL==-1)

        parent平衡因子置0

        subL平衡因子置1

        subLR平衡因子放置0

(2)c插入(subRL==1)

        parent平衡因子置-1

        subL平衡因子置0

        subLR平衡因子放置0

(3)本身是新插节点(subRL==0)

        parent平衡因子置0

        subL平衡因子置0

        subLR平衡因子放置0

总结

1、双旋可以直接调用所需要的单选

2、旋转之后,高度回到插入之前,不会影响到其他祖先,直接退出调整。

旋转之后,高度回到插入之前,不会影响到其他祖先,直接退出

五、AVL树的验证

1、中序遍历

利用递归子函数套一层,传递传递根节点,开始遍历

(不能说明是一颗AVL树,还需要验证平衡)

2、AVL平衡检查

        不能利用层序或者节点存储的平衡因子来验证AVL是否平衡,层序无法知道下一层节点所在的具体位置,而存储的平衡因子可能本来就有错误,用错误数值判断,是一种监守自盗;

检查思路:

1、递归,通过平衡因子定义,计算每个节点的实际平衡因子(右高-左高)

2、检查每个节点的存储平衡因子对不对(平衡因子是否异常)

3、检查平衡因子绝对值是不是小于2(平衡因子是否异常)

4、必须判断完所有节点,才能返回true

前序检查

重复计算子树的高度,效率很低

后序检查

1、边判断平衡,边求出高度

2、设置高度应用参数,子树递归后,将子树的高度从深一层的递归当中带出来,即把子树高度带给上一层(核心)

3、把判断写在前面,先走到最深层,倒着走会根节点,逐个判断是否平衡

3、调试技巧

1、每插入一个数据后,用isblance(),看是插入谁导致出现的问题

2、在插入问题数据之前,打条件断点,画出插入前的树

3、单步跟踪,对比图一一分析细节原因

六、AVL树的性能

        AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即log_2 (N)。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。

        随机插入效率很高,查找效率也很高,在release模式下极快

七、AVL模拟代码

#pragma once

template<class K, class V >
struct AVLTreeNode {
    typedef AVLTreeNode<K, V> Node;
    Node* _parent;
    Node* _left;
    Node* _right;
    int _bf;
    pair<K, V> _kv;

    AVLTreeNode(const pair<K, V>& kv)
        :_parent(nullptr),
        _left(nullptr),
        _right(nullptr),
        _bf(0),
        _kv(kv)
    {}
};

template<class K, class V >
class AVLTree {
    typedef AVLTreeNode<K, V> Node;
public:
    bool Insert(const pair<K, V>& kv) {
        if (_root == nullptr) {
            _root = new Node(kv);
            return true;
        }
        Node* parent = nullptr;
        Node* cur = _root;
        while (cur) 
        {
            if (kv.first > cur->_kv.first)
            {
                parent = cur;
                cur = cur->_right;
            }
            else if (kv.first < cur->_kv.first)
            {
                parent = cur;
                cur = cur->_left;
            }
            else
                return false;
        }
        cur = new Node(kv);
        if (kv.first < parent->_kv.first)
            parent->_left = cur;
        else
            parent->_right = cur;

        //将新节点与parent链接
        cur->_parent = parent;


        while (parent)
        {
            //更新原则
            //新增节点是父亲的左边,父亲的平衡因子加一,
            //是父亲的右边,父亲的平衡因子减一。
            if (parent->_left == cur)
                parent->_bf--;
            else
                parent->_bf++;


            //是否更新取决于parent的高度是否发生变化,是否影响到了爷爷节点

            //1、更新后parent的_bf为0,说明parent所在的子树高度不变,不会影响到爷爷节点的平衡因子
            //(更新前,parent的_bf为1或者-1,更新后左右均衡了,parent的高度不变,不会影响到爷爷节点)
            if (parent->_bf == 0)
                break;
            //2、更新后parent的_bf为1或者-1,parent所在的子树高度变了,会影响到爷爷节点,
            //(更新前parent的_bf是0,parent的一边插入后不均衡了),继续向上更新爷爷节点
            else if (parent->_bf == 1 || parent->_bf == -1)
            {
                cur = parent;
                parent = cur->_parent;
            }
            //3、更新后parent的_bf为2或者-2,parent违反了平衡规则,需要进行旋转处理
            else if (parent->_bf == 2 || parent->_bf == -2)
            {
                //左旋
                if (parent->_bf == 2 && cur->_bf == 1)
                {
                    RotateL(parent);
                    //左旋后,树的高度没有改变,不影响爷爷节点,直接退出
                }
                //右旋
                else if (parent->_bf == -2 && cur->_bf == -1)
                {
                    RotateR(parent);
                }
                //先左旋,后右旋
                else if (parent->_bf == -2 && cur->_bf == 1)
                {
                    RotateLR(parent);
                }
                //先右旋,后左旋
                else if (parent->_bf == 2 && cur->_bf == -1)
                {
                    RotateRL(parent);
                }
                //旋转后,相比插入之前,树的高度不变,不会影响到祖先节点,直接退出
                break;
            }
            else
            {
                //插入前就有问题
                assert(false);
            }
        }
        return true;
    }
    //左旋
    void RotateL(Node* parent)
        {
            Node* subR = parent->_right;
            Node* subRL = subR->_left;

            //处理parent与subRL新关系的链接
            parent->_right = subRL;
            //subRL不为空,其父节点指向parent
            if (subRL)
                subRL->_parent = parent;

            //处理parent与subR新关系的链接
            subR->_left = parent;
            Node* ppnode = parent->_parent;//保存旧parent的父亲节点与subR节点链接
            parent->_parent = subR;

            //处理parent的父亲节点与subR节点链接
            //parent可能是根节点
            if (_root == parent)
            {
                _root = subR;
                subR->_parent = nullptr;
            }
            else
            {
                if (parent == ppnode->_left)
                    ppnode->_left = subR;
                else
                    ppnode->_right = subR;
                subR->_parent = ppnode;
            }

            //平衡因子调整
            parent->_bf = 0;
            subR->_bf = 0;
        }
    //右旋
    void RotateR(Node* parent)
    {
        Node* subL = parent->_left;
        Node* subLR = subL->_right;

        parent->_left = subLR;
        if (subLR)
            subLR->_parent = parent;

        subL->_right = parent;
        Node* ppnode = parent->_parent;
        parent->_parent = subL;

        if (parent == _root)
        {
            _root = subL;
            subL->_parent = nullptr;
        }
        else
        {
            if (ppnode->_left == parent)
            {
                ppnode->_left = subL;
            }
            else
            {
                ppnode->_right = subL;
            }
            subL->_parent = ppnode;
        }

        parent->_bf = 0;
        subL->_bf = 0;
    }
    //左右旋
    void RotateLR(Node* parent)
    {
        Node* subL = parent->_left;
        Node* subLR = subL->_right;

        //通过subLR的平衡因子确定新插的节点在什么位置
        //分情况更新平衡因子
        int bf =subLR->_bf;
    
        //先对左节点左旋,在对parent节点右旋
        RotateL(subL);
        RotateR(parent);
        //h=0的情况,subLR本身就是新增节点
        if (bf == 0)
        {
            subLR->_bf = 0;
            parent->_bf=0;
            subL->_bf = 0;
        }
        //插在中右
        else if (bf == 1)
        {
            subLR->_bf = 0;
            parent->_bf = 0;
            subL->_bf = -1;
        }
        //插在中左
        else if (bf == -1)
        {
            subLR->_bf = 0;
            parent->_bf = 1;
            subL->_bf = 0;
        }
        else
        {
            assert(false);
        }
    }
    //右左旋
    void RotateRL(Node* parent) {
        Node* subR = parent->_right;
        Node* subRL = subR->_left;

        //通过subRL的平衡因子确定新插的节点在什么位置
        //分情况更新平衡因子
        int bf = subRL->_bf;

        //先对右节点右旋,然后对parent节点左旋
        RotateR(subR);
        RotateL(parent);

        //更新平衡因子
        //h=0的情况,subLR本身就是新增节点
        if (bf == 0)
        {
            subRL->_bf = 0;
            parent->_bf = 0;
            subR->_bf = 0;
        }
        //插在中右
        else if (bf == 1)
        {
            subRL->_bf = 0;
            parent->_bf = -1;
            subR->_bf = 0;
        }
        //插在中左
        else if (bf == -1)
        {
            subRL->_bf = 0;
            parent->_bf = 0;
            subR->_bf= 1;
        }
        else
        {
            assert(false);
        }
    }

    //中序遍历
    void Inorder()
    {
        _inorder(_root);
        cout << "end"<<endl;
    }
    void _inorder(Node* root)
    {
        if (root == nullptr)
            return;
        _inorder(root->_left);
        cout << root->_kv.first << ":" << root->_kv.second << endl;
        _inorder(root->_right);
    }
   

    //检查是否满足AVL平衡
    bool isBalance()
    {
        //前序递归检查,效率低
        //return isblance(_root);

        //后序递归检查
        int height = 0;
        return _isBalance(_root,height);
    }
    //前序递归检查,效率低
    bool isbalance(Node* root)
    {
        if (root == nullptr)
            return true;
        int bf = height(root->_right) - height(root->_left);
        if (abs(bf) >= 2)
        {
            cout << root->_kv.first << "不平衡" << endl;
            return false;
        }
        if (bf != root->_bf)
        {
            cout << root->_kv.first << "平衡因子异常" << endl;
            return false;
        }
        return isbalance(root->_left) && isbalance(root->_right);
    }
    //改为后序递归检查
    bool _isBalance(Node* root, int &height) 
    {
        //增加高度应用参数,将递归得到的子树高度保存,返还给上一级
        //                   很巧妙的设计
        if (root == nullptr)
        {
            height = 0;
            return true;
        }
        int lefth, righth;
        //存在子树不平衡,就返回false
        if (!_isBalance(root->_left,lefth) || !_isBalance(root->_right,righth))
        {
            return false;
        }

        int bf = righth-lefth;
        if (abs(bf) >= 2)
        {
            cout << root->_kv.first << "不平衡" << endl;
            return false;
        }
        //根据实际算出来的平衡因子,和存储的平衡因子对比
        if (bf != root->_bf)
        {
            cout << root->_kv.first << "平衡因子异常" << endl;
            return false;
        }
        height = lefth > righth ? lefth + 1 : righth + 1;
        return true;
    }



    //求当前树的高度
    int Height()
    {
        return height(_root);
    }
    //求高度子函数
    int height(Node* root)
        {
            if (root == nullptr)
                return 0;
            int lefth = height(root->_left);
            int righth = height(root->_right);
            return lefth > righth ? lefth + 1 : righth + 1;
        }
    //树的节点个数
    size_t Size()
    {
        return _Size(_root);
    }
    size_t _Size(Node* root)
    {
        if (root == NULL)
            return 0;

        return _Size(root->_left)
            + _Size(root->_right) + 1;
    }


    //查找节点
    Node* Find(const K& key)
    {
        Node* cur = _root;
        while (cur)
        {
            if (cur->_kv.first < key)
            {
                cur = cur->_right;
            }
            else if (cur->_kv.first > key)
            {
                cur = cur->_left;
            }
            else
            {
                return cur;
            }
        }

        return NULL;
    }
   
private:
    Node* _root = nullptr;
};

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值