前言
前面对map/multimap/set/multiset
进行了简单的介绍,我们发现这几个容器有个共同点是:其底层都是按照二叉搜索树来实现的,但是二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此map、set
等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。
而AVL树和红黑树是常用的自平衡二叉搜索树。它们在插入、删除和查找操作上具有较好的性能,并且在各种应用场景中被广泛使用。
1.AVL树
1.1 AVL树的定义
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii
和E.M.Landis
在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度,如下图所示,每个节点都有一个平衡因子:
该平衡因子是由左子树的高度减去右子树的高度得来的(当然也可以选择使用右子树的高度减去左子树的高度),当平衡因子的大小大于等于2或小于等于-2时,说明左右高度差超过1,就需要旋转来维持平衡,这也是平衡因子的作用。
1.2 AVL树的性质
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在
,搜索时间复杂度O(
)
1.3 AVL树的节点
那么AVL树节点的内容除了左右子树的指针以及存储数据的类型,还需要保存该节点的平衡因子,也就是说AVL树每个节点都包含一个平衡因子,一旦该节点的平衡因子大于等于2或小于等于-2就需要进行旋转,维持平衡:
代码语言:javascript复制struct AVLTreeNode
{
AVLTreeNode<T>* _pLeft;
AVLTreeNode<T>* _pRight;
AVLTreeNode<T>* _pParent;//父节点指针
T _data; //存储数据
int _bf; // 节点的平衡因子
//默认构造函数
AVLTreeNode(const T& data = T())
: _pLeft(nullptr)
, _pRight(nullptr)
, _pParent(nullptr)
, _data(data)
, _bf(0)
{}
};
除了平衡因子,我们发现每个节点都保存了父节点的指针,这是因为旋转或删除一个节点之后,父节点的平衡因子可能也需要改变,所以需要保存。
2.红黑树
2.1 红黑树的定义
红黑树,是一种自平衡的二叉搜索树,它在每个结点上增加一个存储位表示结点的颜色来保持平衡,每个节点的颜色可以是Red或Black。
2.2 红黑树的性质
红黑树的节点可以是红色或黑色,满足以下性质:
- 根节点是黑色的。
- 如果一个节点是红色的,则它的两个子节点都是黑色的。
- 从任意节点到其每个叶子节点的路径上包含相同数量的黑色节点。
- 所有叶子节点(NIL节点,即空节点)是黑色的。
如下图:
红黑树通过对任何一条从根到叶子的路径上各个结点颜色的限制,确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
这是因为根据红黑树的性质,其最短路径如果存在则应该是全部都是黑节点,最长路径如果存在则应该是一黑一红交错的路径,这样最长路径是无论如何都不会大于最短路径的两倍,也就相当于最长路径不会大于其他任何路径的两倍,保证了红黑树的相对平衡。
2.3 红黑树的节点
代码语言:javascript复制// 节点的颜色
enum Color { RED, BLACK };
// 红黑树节点的定义
template<class ValueType>
struct RBTreeNode
{
RBTreeNode<ValueType>* _pLeft; // 节点的左孩子
RBTreeNode<ValueType>* _pRight; // 节点的右孩子
RBTreeNode<ValueType>* _pParent; // 节点的双亲(红黑树需要旋转,为了实现简单给出该字段)
ValueType _data; //节点的值域
Color _color; // 节点的颜色
//默认构造函数
RBTreeNode(const ValueType& data = ValueType(),Color color = RED)
: _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr)
, _data(data), _color(color)
{}
};
红黑树的节点与AVL树一样需要父节点的指针,因为红黑树在插入新节点或删除节点时会出现不满足红黑树性质的情况,这时红黑树需要旋转来维持相对平衡,为了实现简单给出父节点指针。
此外对于默认构造函数,我们发现新增1一个节点默认给的是红色,这是因为如果给的是黑色,那么新增节点的路径上就多了一个黑色节点,为了满足红黑树所有路径上黑色节点数目相等就必须改变其他节点的颜色;而如果新增节点给的是红色,那么如果父节点是黑色我们就不需要做改动,如果父节点是红色我们才需要做改动,有一半的可能不需要改动,所以我们选择将新增节点默认设为为红色。
3.结语
使用AVL树和红黑树时,可以按照二叉搜索树的规则进行插入、删除和查找操作。由于它们的自平衡特性,插入和删除操作可能需要进行旋转或颜色调整,以确保树的平衡性。这些操作可以保证树的高度保持在O(logn)
,从而提供了较好的性能。
在实际应用中,AVL树和红黑树都可以用于需要高效的插入、删除和查找操作的场景,例如数据库中的索引结构、编译器中的符号表等。在选择使用哪种树结构时,可以根据具体的应用需求和性能要求进行评估和选择。以上就是今天所有的内容啦~ 完结撒花~