AVL 树的简单介绍
普通二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序,二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。
AVL 树就可以解决上述问题,让搜索树的查找效率在任何情况下都能稳定是 O(logN)。
AVL 树解决上述问题的方法是:保证每个结点的左右子树高度之差的绝对值不超过 1。这样就能保证树中的节点分布接近满二叉树,高度非常接近 logN(N 为树中节点的个数),进而让一次查找的效率为 O(logN)。
为什么是保证每个结点的左右子树高度之差的绝对值不超过 1,而不是保证左右子树高度一样呢?其实很简单:因为在一些情况下绝对不可能做到左右子树高度一样,例如某些特定形态的树,无论如何改变树的形态,都不可能做到每个结点的左右子树高度相等。
综上:一棵 AVL 树或者是空树,或者是具有以下性质的二叉搜索树:
- 它的左右子树都是 AVL 树
- 左右子树高度之差 (简称平衡因子) 的绝对值不超过 1
准备工作
创建头文件 AVLTree.hpp 和源文件 test.cpp。
【因为模板的声明和定义不能分处于不同的文件中,所以把成员函数的声明和定义放在了同一个文件 AVLTree.hpp 中】
AVLTree.hpp:存放包含的头文件,命名空间的定义,成员函数和命名空间中的函数的定义test.cpp:存放 main 函数,以及测试代码
包含头文件
- iostream:用于输入输出
- cmath:提供数学函数
类的成员变量
实现 AVL 树最主要的就是保证树中节点的左右子树高度差不超过 1。而维护这一条件的方法并不是唯一的,我使用的是平衡因子来维护。
平衡因子是每个节点都有的,它的值就是这个节点的左右子树高度之差(一般是右子树高度 - 左子树高度)。
构造函数和拷贝构造
构造函数没什么好说的,默认构造就行了:
AVLTree() : _root(nullptr) { }
拷贝构造:因为节点都是从堆区 new 出来的,所以要深拷贝。 使用递归实现深拷贝:因为拷贝构造不能有多余的参数,但是递归函数又必须使用参数记录信息。 然后在拷贝构造里面调用一下这个函数就行了:
AVLTree(const AVLTree& obj) { _root = Copy(obj._root, nullptr); }
swap 和赋值运算符重载
交换两颗二叉搜索树的本质就是交换两颗树的资源(数据),而它们的资源都是从堆区申请来的,然后用指针指向这些资源。并不是把资源存储在了二叉搜索树对象中。
所以资源交换很简单,直接交换 _root 指针的指向即可:
void Swap(AVLTree& obj) { std::swap(_root, obj._root); }
赋值运算符重载:
AVLTree& operator=(AVLTree obj) { this->Swap(obj); return *; }


