C++实现AVL树

AVL树的名字来源于它的发明作者G.M. Adelson-Velsky 和 E.M. Landis。AVL树是最先发明的自平衡二叉查找树(Self-Balancing Binary Search Tree,简称平衡二叉树)。

一棵AVL树有如下必要条件:

1.它必须是二叉查找树。
2.每个节点的左子树和右子树的高度差至多为1。
C++实现AVL树_第1张图片
左边二叉树的节点45左子树高度2,右子树高度0,左右子树高度差为2-0=2,不满足条件二;
右边二叉树的节点均满足左右子树高度差至多为1,同时它满足二叉搜索树的要求,因此它是一棵平衡二叉树。

AVL树的查找、插入、删除操作在平均和最坏的情况下都是O(logn),这得益于它时刻维护着二叉树的平衡。如果我们需要查找的集合本身没有顺序,在频繁查找的同时也经常的插入和删除,AVL树是不错的选择。不平衡的二叉查找树在查找时的效率是很低的,因此,AVL如何维护二叉树的平衡是我们的学习重点。
平衡因子:将二叉树上节点的左子树高度减去右子树高度的值称为该节点的平衡因子BF(Balance Factor)。
在图二右边的AVL树上:
节点50的左子树高度为3,右子树高度为2,BF= 3-2 = 1;
节点45的左子树高度为2,右子树高度为1,BF= 2-1 = 1;
节点46的左子树高度为0,右子树高度为0,BF= 0-0 = 0;
节点65的左子树高度为0,右子树高度为1,BF= 0-1 = -1;
对于平衡二叉树,BF的取值范围为[-1,1]。如果发现某个节点的BF值不在此范围,则需要对树进行调整。
最小不平衡子树:距离插入节点最近的,且平衡因子的绝对值大于1的节点为根的子树。

C++实现AVL树_第2张图片
在上图中,左边二叉树的节点45的BF = 1,插入节点43后,节点45的BF = 2。节点45是距离插入点43最近的BF不在[-1,1]范围内的节点,因此以节点45为根的子树为最小不平衡子树。

回到顶部
AVL树的实现详解
1. 节点结构

struct AVLTreeNode
{
    AVLTreeNode(T value, AVLTreeNode<T>*l, AVLTreeNode<T>*r)
    :key(value), lchild(l), rchild(r){}

    T key;
    int height;
    AVLTreeNode<T>* lchild;
    AVLTreeNode<T>* rchild;
};

AVL的节点结构为AVLTreeNode,它包括:

key:节点的值
height: 节点的高度,用于计算父节点的平衡因子
lchild : 若节点有左子树,则lchild指向节点的左孩子,否则指向nullptr
rchild : 若节点有右子树,则rchild指向节点的右孩子,否则指向nullptr
在另外一些AVL实现的节点设计方案中,会把BF作为结点的一个属性存储起来,而在这里我们存储的是节点的高度,通过节点的高度我们也可以间接计算出节点的BF。例如节点A的左孩子的height = 2,右孩子的height = 1,那么节点A的平衡因子为2 - 1 = 1.

  1. AVL树的抽象数据结构(ADT)
templateT>
class AVLTree
{
public:
    AVLTree();            //构造函数
    ~AVLTree();            //析构函数

    void preOrder();    //前序遍历AVL树
    void InOrder();        //中序遍历AVL树   
    void postOrder();    //后序遍历AVL树

    void print();        //打印AVL树
    void destory();        //销毁AVL树

    void insert(T key);    //插入指定值的节点
    void remove(T key);    //移除指定值的节点

    AVLTreeNode<T>* search_recurse(T key);    //利用递归算法进行指定值的查找
    AVLTreeNode<T>* search_iterator(T key);    //利用迭代算法进行指定值的查找
    T minimum();        //返回AVL中的最小值
    T maximum();        //返回AVL中的最大值

    int height();        //返回树的高度

private:
    AVLTreeNode<T>* root;    //AVL树的根节点

private:
    void preOrder(AVLTreeNode<T>* pnode) const;
    void inOrder(AVLTreeNode<T>* pnode) const;
    void postOrder(AVLTreeNode<T>* pnode) const;

    void print(AVLTreeNode<T>* pnode,T key, int direction) const;
    void destory(AVLTreeNode<T>* & pnode);

    AVLTreeNode<T>* insert(AVLTreeNode<T>* &pnode, T key);       
    AVLTreeNode<T>* remove(AVLTreeNode<T>* & pnode, AVLTreeNode<T>* pdel); //删除AVL树中节点pdel,并返回被删除的节点

    AVLTreeNode<T>* minimum(AVLTreeNode<T>*pnode)const;
    AVLTreeNode<T>* maximum(AVLTreeNode<T>*pnode)const;

    AVLTreeNode<T>* search_recurse(AVLTreeNode<T>* pnode, T key) const;
    AVLTreeNode<T>* search_iterator(AVLTreeNode<T>* pnode, T key) const;

    AVLTreeNode<T>* leftRotation(AVLTreeNode<T>* pnode);        //单旋:左旋操作
    AVLTreeNode<T>* rightRotation(AVLTreeNode<T>* pnode);        //单旋:右旋操作
    AVLTreeNode<T>* leftRightRotation(AVLTreeNode<T>* pnode);    //双旋:先左旋后右旋操作
    AVLTreeNode<T>* rightLeftRotation(AVLTreeNode<T>* pnode);    //双旋:先右旋后左旋操作

};

这里我们定义了AVL树这个类型AVLTree。它包含了:

AVL树的根节点root,这是唯一的数据成员
操作的外部接口与内部实现接口。例如 preOrder()为提供给用户使用的接口,接口声明为public;而preOrder(AVLTreeNode* pnode)是类内部为了递归操作所使用的接口,接口声明为private。
旋转操作(rotation)用来调整失去平衡的二叉树,四个内部接口针对四种失衡情况进行调整,后面详细解释。
3. AVL树的高度

如前所说,我们的节点结构中并不存储结点的BF,取而代之的是节点的高度。一个节点的BF可由其左右子树的高度计算出来。我们提供返回一个节点高度的操作

/*返回一棵树的高度*/
template <typename T>
int AVLTree::height(AVLTreeNode* pnode)
{
    if (pnode != nullptr)
    {
        return pnode->height;
    }
    return 0;                                                                //如果是空树,高度为0
};

template <typename T>
int AVLTree::height()
{
    return height(root);
};
  1. AVL树失衡调整

节点的插入或删除都有可能导致AVL树失去平衡,因此,失衡调整是插入与删除操作的基础。
AVL树的失衡调整可以分为四种情况,我们逐一分析。
假设我们要为数组a[]={4,5,6,3,2,8,7,0,1}构建一棵AVL树。

情况一:左单旋转

首先插入{4,5,6},在插入元素6后出现不平衡的情况:

当我们在右子树插入右孩子导致AVL失衡时,我们需要进行单左旋调整。旋转围绕最小失衡子树的根节点进行。
在删除新节点时也有可能会出现需要单左旋的情况。
左旋代码如下:

/*左旋转操作*/
/*pnode为最小失衡子树的根节点*/
/*返回旋转后的根节点*/
template<typename T>
AVLTreeNode<T>* AVLTree<T>::leftRotation(AVLTreeNode<T>* proot)
{
    AVLTreeNode<T>* prchild = proot->rchild;
    proot->rchild = prchild->lchild;
    prchild->lchild = proot;

    proot->height = max(height(proot->lchild),height(proot->rchild))+1;     //更新节点的高度值
    prchild->height = max(height(prchild->lchild), height(prchild->rchild)) + 1; //更新节点的高度值

    return prchild;                    
};

结合例子进行分析:

参数proot为最小失衡子树的根节点,在图四中为节点4
若节点5有左子树,则该左子树成为节点4的右子树
节点4成为节点5的左子树
最后更新节点的高度值
情况二:右单旋转

我们继续插入元素{3,2},此时二叉树为:

插入3、2后出现了不平衡的情况。此时的插入情况是“在左子树上插入左孩子导致AVL树失衡”,我们需要进行单右旋调整。
单右旋代码为:

/*右旋转操作*/
/*pnode为最小失衡子树的根节点*/
/*返回旋转后的根节点*/
template <typename  T>
AVLTreeNode<T>* AVLTree<T>::rightRotation(AVLTreeNode<T>*proot)
{
    AVLTreeNode<T>* plchild = proot->lchild;
    proot->lchild = plchild->rchild;
    plchild->rchild = proot;

    proot->height = max(height(proot->lchild), height(proot->rchild)) + 1;     //更新节点的高度值
    plchild->height = max(height(plchild->lchild), height(plchild->rchild)) + 1; //更新节点的高度值

    return plchild;
};

结合例子进行分析:

参数proot为最小失衡子树的根节点,在图四中为节点4
若节点3有右子树,则该右子树成为节点4的左子树
节点4成为节点3的左子树
调整节点的高度值
情况三:先左旋后右旋

需要进行两次旋转的原因是第一次旋转后,AVL树仍旧处于不平衡的状态,第二次旋转再次进行调整。
我们继续插入元素{8,7}

这种情况,总结起来就是“在右子树上插入左孩子导致AVL树失衡”,此时我们需要进行先右旋后左旋的调整。
调整的代码为:

/先右旋再左旋/
/参数proot为最小失衡子树的根节点/
/返回旋转后的根节点/
template
AVLTreeNode* AVLTree::rightLeftRotation(AVLTreeNode* proot)
{
proot->rchild = rightRotation(proot->rchild);
return leftRotation(proot);
};
结合例子进行分析:

首先对最小不平衡子树的根节点(也就是节点6)的右孩子(也就是8)进行右旋操作
再对节点6进行一次左旋操作
情况四:先左旋后右旋

根据对称性原理,当我们“在左子树上插入右孩子导致AVL树失衡”,此时我们需要进行先左旋后右旋的调整。如果你不理解接着看图。
我们接着插入节点{0,1}

调整的代码:

/*先左后右做旋转*/
/*参数proot为最小失衡子树的根节点*/
/*返回旋转后的根节点*/
template <typename T>
AVLTreeNode<T>* AVLTree<T>::leftRightRotation(AVLTreeNode<T>* proot)
{
    proot->lchild= leftRotation(proot->lchild);
    return rightRotation(proot);
};

。。。。。。。。。。。。。。。。。。。。

你可能感兴趣的:(算法与数据结构)