目录
1. AVL树的概念
2. AVL树结点和树的定义
3. AVL树的插入(未包含旋转)
4. AVL树的旋转
4.1 右右_左单旋
4.2 左左_右单旋
4.3 左右双旋
4.4 右左双旋
5. AVL树的验证
6. AVL树的删除(了解)和性能
7. AVL树插入验证完整代码
8. AVL树笔试选择题
答案:
本章完。
二叉搜索树虽然可以提高我们查找数据的效率,但如果插入二叉搜索树的数据是有序或接近有序的,此时二叉搜索树会退化为单支树,在单支树当中查找数据相当于在单链表当中查找数据,效率是很低下的。
因此,两位俄罗斯的数学家G.M.A delson-Velskii和E.M.Landis在1962年发明了解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
AVL树可以是一棵空树,也可以是具有以下性质的一棵二叉搜索树:
树的左右子树都是AVL树。
树的左右子树高度之差(简称平衡因子)的绝对值不超过1(-1 / 0 / 1)。
如果一棵二叉搜索树的高度是平衡的,它就是AVL树。如果它有n个结点,
其高度可保持在O(logN),搜索时间复杂度也是O(logN)。
注意: 这里所说的二叉搜索树的高度是平衡的是指,树中每个结点左右子树高度之
差的绝对值不超过1,因为只有满二叉树才能做到每个结点左右子树高度之差均为0。
我们这里直接实现KV模型的AVL树,为了方便后续的操作,
这里将AVL树中的结点定义为三叉链结构,并在每个结点当中引入平衡因子,
(右子树高度-左子树高度)。除此之外,还需编写一个构造新结点的构造函数,
由于新构造结点的左右子树均为空树,于是将新构造结点的平衡因子初始设置为0即可。
(平衡因子并不是AVL树所必须的,这里只是其中一种实现方法)
AVLTree.h:
#pragma once
#include
using namespace std;
template
struct AVLTreeNode
{
AVLTreeNode* _left;
AVLTreeNode* _right;
AVLTreeNode* _parent;
pair _kv; // 存的键值
int _bf; // balance factor 平衡因子
AVLTreeNode(const pair& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
, _bf(0)
{}
};
template
class AVLTree
{
typedef AVLTreeNode Node;
protected:
Node* _root = nullptr; // 给缺省值直接在初始化列表初始化
};
VL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。
那么AVL树的插入过程可以分为两步:
① 按照二叉搜索树的方式插入新节点
与根结点比较如果比根大就往右子树插入,如果比根小就往左子树插入,
直到走到合适的位置就插入,由于这里是三叉链所以需要处理结点之间的关联关系
② 调整节点的平衡因子
当左右子树的高度发生了变化,那么就需要对父亲及祖先路径上的所有结点的平衡因子进行调整
插入结点后需要倒着往上更新平衡因子,更新规则如下:
1. 新增结点在parent的右边,parent的平衡因子+ +。
2. 新增结点在parent的左边,parent的平衡因子− −。
每更新完一个结点的平衡因子后,都需要进行以下判断:
如果parent的平衡因子等于0,表明无需继续往上更新平衡因子了。
如果parent的平衡因子等于-1或者1,表明还需要继续往上更新平衡因子。
如果parent的平衡因子等于-2或者2,表明此时以parent结点为根结点的子树已经不平衡了,
需要进行旋转处理。
bool Insert(const pair& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur) // 找要插入的位置
{
if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->right;
}
else
{
return false;
}
}
cur = new Node(kv);
if (kv.first < parent->_kv.first) // 插入要插入的位置
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
cur->_parent = parent; // 三叉链多一步
while (parent) // 控制平衡, 更新平衡因子, 如果平衡因子不对, 就要旋转
{
if (cur == parent->_left)
{
parent->_bf--;
}
else
{
parent->_bf++;
}
if (parent->_bf == 0)
{
break;
}
else if (abs(parent->_bf) == 1) // 往上更新
{
parent = parent->_parent;
cur = cur->_parent;
}
else if (abs(parent->_bf) == 2) // 不平衡了,需旋转
{
// 后面写
}
else // 理论不可能走到这,除非之前就错了
{
assert(false); // 报个错
}
}
return true;
}
若是在更新平衡因子的过程当中,出现了平衡因子为-2/2的结点,
这时我们需要对以该结点为根结点的树进行旋转处理,而旋转处理分为四种,
在进行分类之前我们首先需要进行以下分析:
当parent的平衡因子为-2/2时,cur的平衡因子必定是-1/1而不会是0。
理由如下:
若cur的平衡因子是0,那么cur一定是新增结点,而不是上一次更新平衡因子时的parent,
否则在上一次更新平衡因子时,会因为parent的平衡因子为0而停止继续往上更新。
而cur是新增结点的话,其父结点的平衡因子更新后一定是-1/0/1,而不可能是-2/2,
因为新增结点最终会插入到一个空树当中,在新增结点插入前,
其父结点的状态有以下两种可能:其父结点是一个左右子树均为空的叶子结点,
其平衡因子是0,新增结点插入后其平衡因子更新为-1/1。
其父结点是一个左子树或右子树为空的结点,其平衡因子是-1/1,
新增结点插入到其父结点的空子树当中,使得其父结点左右子树当中较矮的一棵子树增高了,
新增结点后其平衡因子更新为0。
综上所述,当parent的平衡因子为-2/2时,cur的平衡因子必定是-1/1而不会是0。
根据此结论,我们可以将旋转处理分为以下四类:
并且,在进行旋转处理后就无需继续往上更新平衡因子了,
因为旋转后树的高度变为插入之前了,即树的高度没有发生变化,
也就不会影响其父结点的平衡因子了。具体原因请看后面的旋转讲解。
可以看到,经过左单旋后,树的高度变为插入之前了,即树的高度没有发生变化,
所以左单旋后无需继续往上更新平衡因子。
左单旋的步骤如下:
左单旋后满足二叉搜索树的性质:
左单旋代码:
void RotateL(Node* parent)
{
Node* subR = parent->_right; // 动了三个标记了的结点,共更新六个指针,这更新两个指针
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL) // subRL不为空才更新
{
subRL->_parent = parent;
}
Node* ppNode = parent->_parent; // 记录parent的parent,防止parent是一颗子树的头结点
subR->_left = parent; // 再更新两个指针
parent->_parent = subR;
if (_root == parent) // 最后更新两个指针
{
_root = subR;
subR->_parent = nullptr;
}
else // parent是一颗子树的头结点
{
if (ppNode->_left == parent)
{
ppNode->_left = subR;
}
else
{
ppNode->_right = subR;
}
subR->_parent = ppNode;
}
subR->_bf = parent->_bf = 0; // 更新平衡因子
}
经过右单旋后,树的高度变为插入之前了,即树的高度没有发生变化,
所以右单旋后无需继续往上更新平衡因子。
右单旋的步骤如下:
右单旋后满足二叉搜索树的性质:
右单旋代码:
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR; // 更新两个节点
if (subLR)
{
subLR->_parent = parent;
}
Node* ppNode = parent->_parent;
subL->_right = parent; // 再更新两个节点
parent->_parent = subL;
if (_root == parent) // 最后更新两个结点
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subL;
}
else
{
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
subL->_bf = parent->_bf = 0; // 更新平衡因子
}
左右双旋步骤示意图
1、插入新结点。这里可能插入到b / c下面,还可能h等于0,插入到60下面
(以上三种插入的后两步都是一样的,只是最后平衡因子不同:)
2、以30为旋转点进行左单旋。
3、以90为旋转点进行右单旋。
左右双旋的步骤如下:
左右双旋后满足二叉搜索树的性质:
左右双旋后,实际上就是让subLR的左子树和右子树,分别作为subL和parent的右子树和左子树,再让subL和parent分别作为subLR的左右子树,最后让subLR作为整个子树的根(结合图理解)。
1. subLR的左子树当中的结点本身就比subL的值大,因此可以作为subL的右子树。
2. subLR的右子树当中的结点本身就比parent的值小,因此可以作为parent的左子树。
3. 经过步骤1/2后,subL及其子树当中结点的值都就比subLR的值小,而parent及其子树当中结点的值都就比subLR的值大,因此它们可以分别作为subLR的左右子树。
观察发现,左右双旋后,平衡因子的更新随着subLR原始平衡因子的不同分为以下三种情况:
1、当subLR原始平衡因子是-1时,左右双旋后subLR,parent、subL的平衡因子分别更新为0、1、0。
2、当subLR原始平衡因子是1时,左右双旋后subLR、parent、subL的平衡因子分别更新为0、0、-1。
3、当subLR原始平衡因子是0时,左右双旋后subLR、parent、subL的平衡因子分别更新为0、0、0。
可以看到,经过左右双旋后,树的高度变为插入之前了,即树的高度没有发生变化,
所以左右双旋后无需继续往上更新平衡因子。
左右双旋代码:
void RotateLR(Node* parent)
{
Node* subL = parent->_left; // 记录subL的平衡因子
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
subLR->_bf = 0; // 三种情况一样
if (bf == -1)
{
parent->_bf = 1;
subL->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = 0;
subL->_bf = -1;
}
else if (bf == 0)
{
parent->_bf = 0;
subL->_bf = 0;
}
else // 理论不应走到这
{
assert(false); //在旋转前树的平衡因子就有问题,报错
}
}
思路和左右双旋一样,这里看图就行
右左双旋步骤示意图
1、插入新结点。
2、以90为旋转点进行右单旋。
3、以30为旋转点进行左单旋。
右左双旋的步骤如下:
右左双旋后满足二叉搜索树的性质:
右左双旋后,实际上就是让subRL的左子树和右子树,分别作为parent和subR的右子树和左子树,再让parent和subR分别作为subRL的左右子树,最后让subRL作为整个子树的根(结合图理解)。
subRL的左子树当中的结点本身就比parent的值大,因此可以作为parent的右子树。
subRL的右子树当中的结点本身就比subR的值小,因此可以作为subR的左子树。
经过步骤1/2后,parent及其子树当中结点的值都就比subRL的值小,
而subR及其子树当中结点的值都就比subRL的值大,因此它们可以分别作为subRL的左右子树。
右左双旋后,平衡因子的更新随着subRL原始平衡因子的不同分为以下三种情况:
1、当subRL原始平衡因子是1时,左右双旋后subRL、parent、subR的平衡因子分别更新为
0、-1、0。
2、当subRL原始平衡因子是-1时,左右双旋后subRL、parent、subR的平衡因子分别更新为
0、0、1。
3、当subRL原始平衡因子是0时,左右双旋后subRL、parent、subR的平衡因子分别更新为
0、0、0。
经过右左双旋后,树的高度变为插入之前了,即树的高度没有发生变化,
所以右左双旋后一样无需继续往上更新平衡因子。
右左双旋代码:
void RotateRL(Node* parent)
{
Node* subR = parent->_right; // 记录subL的平衡因子
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(parent->_right);
RotateL(parent);
subRL->_bf = 0; // 三种情况一样
if (bf == -1)
{
parent->_bf = 0;
subR->_bf = 1;
}
else if (bf == 1)
{
parent->_bf = -1;
subR->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subR->_bf = 0;
}
else // 理论不应走到这
{
assert(false); //在旋转前树的平衡因子就有问题,报错
}
}
AVL树是在二叉搜索树的基础上加入了平衡性的限制,也就是说AVL树也是二叉搜索树,
因此我们可以先获取二叉树的中序遍历序列,来判断二叉树是否为二叉搜索树。
void InOrder()
{
_InOrder(_root)
}
protected:
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << endl;
_InOrder(root->_right);
}
但中序有序只能证明是二叉搜索树,要证明二叉树是AVL树还需验证二叉树的平衡性,
在该过程中我们可以顺便检查每个结点当中平衡因子是否正确。
采用后序遍历,变量步骤如下:
从叶子结点处开始计算每课子树的高度。(每棵子树的高度 = 左右子树中高度的较大值 + 1)
求高度函数以前写过了:
int Height(Node* root)
{
if (root == nullptr)
{
return 0;
}
return max(Height(root->_left), Height(root->_right)) + 1;
}
先判断左子树是否是平衡二叉树。再判断右子树是否是平衡二叉树。
若左右子树均为平衡二叉树,则返回当前子树的高度给上一层,
继续判断上一层的子树是否是平衡二叉树,直到判断到根为止。
(若判断过程中,某一棵子树不是平衡二叉树,则该树也就不是平衡二叉树了)
bool IsBalance()
{
return _IsBalance(_root);
}
protected:
bool _IsBalance(Node* root)
{
if (root == nullptr)
{
return true;
}
int leftHT = Height(root->_left);
int rightHT = Height(root->_right);
int diff = rightHT - leftHT;
if (diff != root->_bf)
{
cout << root->_kv.first << "平衡因子异常" << endl;
cout << rightHT << " - " << leftHT << endl;
return false;
}
return abs(diff) < 2
&& _IsBalance(root->_left)
&& _IsBalance(root->_right);
}
AVL树的删除(了解)
AVL树的删除和其它接口这里不实现了,如果不是AVL树会考到,是不用学的,
因为实际可以用接下来学的红黑树替代它。
因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,
然后再更新平衡因子,只不错与删除不同的时,删除节点后的平衡因子更新,
最差情况下一直要调整到根节点的位置。
具体实现参考《数据结构 - 用面向对象方法与C++描述》殷人昆版。
AVL树的性能
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,
这样可以保证查询时高效的时间复杂度,即O(logN)。
但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:
插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,
有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,
而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。
AVLTree.h:
#pragma once
#include
#include
#include
#include
using namespace std;
template
struct AVLTreeNode
{
AVLTreeNode* _left;
AVLTreeNode* _right;
AVLTreeNode* _parent;
pair _kv; // 存的键值
int _bf; // balance factor 平衡因子
AVLTreeNode(const pair& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
, _bf(0)
{}
};
template
class AVLTree
{
typedef AVLTreeNode Node;
public:
bool Insert(const pair& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur) // 找要插入的位置
{
if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else
{
return false;
}
}
cur = new Node(kv);
if (kv.first < parent->_kv.first) // 插入要插入的位置
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
cur->_parent = parent; // 三叉链多一步
while (parent) // 控制平衡, 更新平衡因子, 如果平衡因子不对, 就要旋转
{
if (cur == parent->_left)
{
parent->_bf--;
}
else
{
parent->_bf++;
}
if (parent->_bf == 0)
{
break;
}
else if (abs(parent->_bf) == 1) // 往上更新
{
parent = parent->_parent;
cur = cur->_parent;
}
else if (abs(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);
}
else // 理论不可能走到这,除非之前就错了
{
//assert(false); // 报个错
}
break;
}
else // 理论不可能走到这,除非之前就错了
{
assert(false); // 报个错
}
}
return true;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
bool IsBalance()
{
return _IsBalance(_root);
}
protected:
bool _IsBalance(Node* root)
{
if (root == nullptr)
{
return true;
}
int leftHT = Height(root->_left);
int rightHT = Height(root->_right);
int diff = rightHT - leftHT;
if (diff != root->_bf)
{
cout << root->_kv.first << "平衡因子异常" << endl;
cout << rightHT << " - " << leftHT << endl;
return false;
}
return abs(diff) < 2
&& _IsBalance(root->_left)
&& _IsBalance(root->_right);
}
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << endl;
_InOrder(root->_right);
}
int Height(Node* root)
{
if (root == nullptr)
{
return 0;
}
return max(Height(root->_left), Height(root->_right)) + 1;
}
void RotateL(Node* parent)
{
Node* subR = parent->_right; // 动了三个标记了的结点,共更新六个指针,这更新两个指针
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL) // subRL不为空才更新
{
subRL->_parent = parent;
}
Node* ppNode = parent->_parent; // 记录parent的parent,防止parent是一颗子树的头结点
subR->_left = parent; // 再更新两个指针
parent->_parent = subR;
if (_root == parent) // 最后更新两个指针
{
_root = subR;
subR->_parent = nullptr;
}
else // parent是一颗子树的头结点
{
if (ppNode->_left == parent)
{
ppNode->_left = subR;
}
else
{
ppNode->_right = subR;
}
subR->_parent = ppNode;
}
subR->_bf = parent->_bf = 0; // 更新平衡因子
}
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR; // 更新两个节点
if (subLR)
{
subLR->_parent = parent;
}
Node* ppNode = parent->_parent;
subL->_right = parent; // 再更新两个节点
parent->_parent = subL;
if (_root == parent) // 最后更新两个结点
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subL;
}
else
{
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
subL->_bf = parent->_bf = 0; // 更新平衡因子
}
void RotateLR(Node* parent)
{
Node* subL = parent->_left; // 记录subL的平衡因子
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
subLR->_bf = 0; // 三种情况一样
if (bf == -1)
{
parent->_bf = 1;
subL->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = 0;
subL->_bf = -1;
}
else if (bf == 0)
{
parent->_bf = 0;
subL->_bf = 0;
}
else // 理论不应走到这
{
assert(false); //在旋转前树的平衡因子就有问题,报错
}
}
void RotateRL(Node* parent)
{
Node* subR = parent->_right; // 记录subL的平衡因子
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(parent->_right);
RotateL(parent);
subRL->_bf = 0; // 三种情况一样
if (bf == -1)
{
parent->_bf = 0;
subR->_bf = 1;
}
else if (bf == 1)
{
parent->_bf = -1;
subR->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subR->_bf = 0;
}
else // 理论不应走到这
{
assert(false); //在旋转前树的平衡因子就有问题,报错
}
}
Node* _root = nullptr; // 给缺省值直接在初始化列表初始化
};
Test.c:
#include "AVLTree.h"
void TestAVLTree1()
{
//int arr[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 }; // 测试单旋平衡因子调节
int arr[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 }; // 测试双旋平衡因子调节
AVLTree t1;
for (const auto& e : arr)
{
t1.Insert(make_pair(e, e));
}
t1.InOrder();
cout << "IsBalance:" << t1.IsBalance() << endl;
}
void TestAVLTree2()
{
size_t N = 10000;
srand(time(0));
AVLTree t1;
for (size_t i = 0; i < N; ++i)
{
int x = rand();
t1.Insert(make_pair(x, i));
//bool ret = t1.IsBalance();
//if (ret == false)
//{
// int u = 1; // 查bug打断点用
//}
//else
//{
// cout << "Insert:" << x << " IsBalance:" << ret << endl;
//}
}
cout << "IsBalance:" << t1.IsBalance() << endl;
}
int main()
{
TestAVLTree1();
return 0;
}
1. 下面关于AVL树说法不正确的是()
A.AVL树也是二叉搜索树
B.极端情况下,AVL树可能也会退化成单支树
C.AVL查询的时间复杂度是O(log_2N)
D.AVL树是通过平衡因子限制保证其平衡性的
2. 现有一棵无重复关键字的平衡二叉树(AVL树),对其进行中序遍历可得到一个降序序列。
下列关于该平衡二叉树的叙述中,正确的是()
A.根结点的度一定为2
B.树中最小元素一定是叶结点
C.最后插入的元素一定是叶结点
D.树中最大元素一定是无左子树
3. 关于AVL树的旋转说法正确的是()
A.插入时,AVL树最多只需要旋转两次
B.删除时,只要某个节点的平衡因子不满足特性时 ,只需要对该棵子树进行旋转,就可以使AVL树再次平衡
C.AVL树的节点中必须维护平衡因子,因为要依靠其平衡因子是否需要旋转以维护其平衡性
D.AVL树的双旋转只需要直接使用对应的单旋转即可
1. B
AVL树:一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树
1. 它的左右子树都是AVL树
2. 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
故:如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在O(logN),搜索时间复杂度O(logN)
A:正确,参考上述概念
B:错误,AVL树没有极端情况,其是为了防止二叉搜索树的极端情况二给出的
C:正确,参考上述概念
D:正确,平衡因子:左右子树高度之间,其绝对值如果不超过1,则认为树就是平衡的
2. D
题目中说:中序遍历得到一个降序序列,则说明:根小于左子树中节点,大于右子树中节点
A:错误,根可以没有左子树,比如树中只有两个节点,即根以及根的右子树
B:错误,树中最小的元素一定是最左侧或者最右侧节点,但不一定是叶子节点
C:错误,最后插入的元素不一定是叶子节点,因为新节点插入后,为了保证其平衡性,还要对树 进行旋转处理,旋 转之后,就不一定在叶子的位置
D:正确,因为最大元素如果存在左子树,中序遍历就不可能是降序序列
3. A
A:正确,即双旋
B:错误,可能需要旋转多次,子树旋转后,其高度降低了一层,其上层可能也需要跟着旋转
C:错误,平衡因子不是必须要维护的,在操作时也可以直接通过高度函数来算,只不过比较麻烦
D:错误,不能直接使用单旋转,因为两个单旋转完成后,还需要对部分节点的平衡因子进行更新
下一篇:红黑树概念和实现。然后是set和map的模拟实现。