AVL 树是最先发明的自平衡二叉查找树,其左右子树都是 AVL 树,且左右子树的高度差的绝对值不超过 1。AVL 树得名于它的发明者 G. M. Adelson-Velsky 和 E. M. Landis,他们在 1962 年的论文中发表了它。
1. AVL 树的概念
在 AVL 树实现中引入一个平衡因子 (balance factor) 的概念,每个结点都有一个平衡因子,任何结点的平衡因子等于右子树的高度减去左子树的高度。也就是说任何结点的平衡因子等于 0/1/-1。AVL 树并不是必须要平衡因子,但是有了平衡因子可以更方便我们去进行观察和控制树是否平衡。
虽然 AVL 树无法做到保证所有子树没有高度差,但每个子树高度差最大就为 2,这就使得 AVL 树整体结点数量和分布和完全二叉树类似,高度以控制在 logN,那么增删查改的效率也可以控制在 O(logN),相比二叉搜索树有了本质的提升。
2. AVL 树的实现
在了解了 AVL 树的概念以及结构特点之后接下来我们就试着来实现 AVL 树。在此和二叉搜索类似在 AVL 树当中我们依旧会实现插入和查找的功能,但在此 AVL 树中的删除功能就没有实现,原因是在 AVL 树当中删除的功能也是在二叉搜索树的删除基础上进行改进的,但结合了旋转之后就较为复杂。
2.1 AVL 树节点结构实现
在 AVL 树当中每个节点内的信息是和二叉搜索树类似,只不过增加两个变量bf 和_parent 分别表示节点内的平衡因子和指向其父节点的指针,其他的和二叉搜索树类型。
template<class T,class K> struct AVLTreeNode { //使用键值对_val 来存储节点内的数据值 pair<T, K> _val; //left 表示指向该节点左孩子节点的指针 AVLTreeNode<T, K>* _left; //right 表示指向该节点右孩子节点的指针 AVLTreeNode<T, K>* _right; //parent 表示指向该节点父节点的指针 AVLTreeNode<T, K>* _parent; //_bf 内存储节点的平衡因子 int _bf; AVLTreeNode(const pair<T, K>& x) :_val(x) ,_left(nullptr) ,_right(nullptr) ,_parent(nullptr) ,_bf(0) { } };
以上结构体 AVLTreeNode 就用于表示 AVL 树的节点,在此实现为类模板可以使得节点内存储任意类型的数据。并且在类当中显示实现构造函数,这样就可以在我们之后创建出节点之后自动的进行初始化的工作。
2.2 AVL 树的结构
在此 AVL 树的结构使用类AVLTree 来封装实现。
template<class T,class K> class AVLTree { //将表示节点的变量重命名,简化书写 typedef AVLTreeNode<T, K> Node; public: //…… private: //root 表示 AVL 树内的根结点指针 Node* root = nullptr; };
2.3 AVL 树插入功能实现
在 AVL 树当中节点插入的步骤其实在前半部分和二叉搜索树节点的插入是完全一样的,只不过是在完成插入之后进行平衡因子的更新;如果当前树的平衡因子绝对值已经超出要求还需要进行旋转的操作。
1. 插入步骤
因此要实现插入的步骤如下:
- 插入一个值按二叉搜索树规则进行插入。
- 新增结点以后,只会影响祖先结点的高度,也就是可能会影响部分祖先结点的平衡因子,所以更新从新增结点->根结点路径上的平衡因子。


