AVL树和红黑树的模拟实现
Posted 玄鸟轩墨
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AVL树和红黑树的模拟实现相关的知识,希望对你有一定的参考价值。
写在前面
我们要遇到C++里面最难一部分了,也是一些大厂有可能出的面试题,比如说手撕一个红黑树.这个博客的内容可能有点耗脑,我尽量和大家分享的接地气一点.
AVL 树
这种树也叫做平衡二叉树.这个是前面二叉搜索树的变种.我们前面谈过,对于比较有序元素,那么搜索二叉树就有点深了,甚至转换成了单只树,那么这样查找的效率就有点低了.所以我们提出的AVL树.所谓的平衡二叉树就是当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度 .这个就是AVL树的定义,至于如何做到,这里我们先不用关心.
我们先来看一下AVL树的特点,一般情况而言,对于那些比较有序的元素,我们插入元素后通过一系列的手段来降低树的高度,并且最后的树仍旧符合二叉搜索树的特点.
AVL树 实现
我们已经知道AVL树是什么了,今天我们就要把他给简单的实现出来.这个过程说实话有点难.我们这里实现KV模型的.不过我们们先来把框架给搭出来.
namespace bit
template<class T,class T>
class AVLTree
;
平衡因子
我们前面把大的框架搭出来了,这里就需要开始搭建树的节点了,我们先暂时看一下,后面完善.这里我们已经搭建出来了,我们为何会加入父节点这个原因我们等到用到的时候再说.
struct AVLTreeNode
AVLTreeNode(const T& data = T())
: _praent(nullptr)
, _left(nullptr)
, _right(nullptr)
, _data(data)
AVLTreeNode<T>* _praent;
AVLTreeNode<T>* _left;
AVLTreeNode<T>* _right;
T _data;
;
现在我们需要在谈一个问题,我们是如何确保左右节点的差距只有一呢?这里需要借助一个平衡因子,父节点的平衡因子记录左右子树高度的差值.这样我们插入的时候就可以看平衡因子的改变,从而判断是不是要移动树了.这里我们平衡因子记录的是右子树减去左子树.至于如何修改平衡因子的值这就有说道了.
template<class T>
struct AVLTreeNode
AVLTreeNode(const T& data = T())
: _praent(nullptr)
, _left(nullptr)
, _right(nullptr)
, _data(data)
, _bf(0)
AVLTreeNode<T>* _praent;
AVLTreeNode<T>* _left;
AVLTreeNode<T>* _right;
T _data;
int _bf;
;
插入元素
现在的的我们大框架已经打好了,我们现在开始重点的工作,这个可是比较烧脑的.我们也是只实现插入操作.
template<class T>
struct AVLTreeNode
AVLTreeNode(const T& data = T())
: _praent(nullptr)
, _left(nullptr)
, _right(nullptr)
, _data(data)
, _bf(0)
AVLTreeNode<T>* _praent;
AVLTreeNode<T>* _left;
AVLTreeNode<T>* _right;
T _data;
int _bf;
;
template<class K, class T>
class AVLTreeV
public:
typedef AVLTreeNode<T> Node;
AVLTree()
:_pRoot(nullptr)
bool Insert(const T& data)
return true;
public:
Node* _pRoot;
;
首先第一步是简单的搜索二叉树的简单操作.我们先来找到插入的节点在哪里.
bool Insert(const T& data)
// 第一步 判断 第一次 插入
if (_pRoot == nullptr)
_pRoot = new Node(data);
_pRoot->_bf = 0;
return true;
// 第二步 搜索二叉树 找位置
Node* cur = _pRoot;
Node* parent = nullptr;
while (cur != nullptr)
if (cur->_data < data)
// 去 右树 查找
parent = cur;
cur = cur->_right;
else if (cur->_data > data)
// 左树 查找
parent = cur;
cur = cur->_left;
else
// 我们这里不接受 重复值 ,你可以自己实现
return false;
// 第三步 new 出来一个节点
Node* node = new Node(data);
node->_bf = 0; // 记住 不要相信构造函数,有可能不是你写的,不会自动默认0
// 第四步 判断是插入左树还是 右树
if (parent->_data > data)
parent->_left = node;
else
parent->_right = node;
// 这里需要链接
node->_parent = parent;
cur = node;
// 更新平衡因子
// ...
return true;
我们这里预留了最关键的一步,就是更新平衡因子.这里需要讨论的事情是在是太多了.我们首先要判断是插入的节点是左树还是右树,这样方便我们修改父节点的平衡因子.
bool Insert(const T& data)
// 第一步 判断 第一次 插入
if (_pRoot == nullptr)
_pRoot = new Node(data);
_pRoot->_bf = 0;
return true;
// 第二步 搜索二叉树 找位置
Node* cur = _pRoot;
Node* parent = nullptr;
while (cur != nullptr)
if (cur->_data < data)
// 去 右树 查找
parent = cur;
cur = cur->_right;
else if (cur->_data > data)
// 左树 查找
parent = cur;
cur = cur->_left;
else
// 我们这里不接受 重复值 ,你可以自己实现
return false;
// 第三步 new 出来一个节点
Node* node = new Node(data);
node->_bf = 0; // 记住 不要相信构造函数,有可能不是你写的,不会自动默认0
// 第四步 判断是插入左树还是 右树
if (parent->_data > data)
parent->_left = node;
else
parent->_right = node;
// 这里需要链接
node->_parent = parent;
cur = node;
// 更新平衡因子
while (parent != nullptr)
// 如果 插入的是 右子树
if (cur == parent->_right)
parent->_bf++;
else
parent->_bf--;
// 开始 检测
if (parent->_bf == 0)
break;
else if (parent->_bf == 1 || parent->_bf == -1)
cur = parent;
parent = parent->_parent;
else if (parent->_bf == 2 || parent->_bf == -2)
// 这里 才是 大头
if (cur->_bf == 1 && parent->_bf == 2)
else if (cur->_bf == -1 && parent->_bf == -2)
// ...
else
assert(false);
return true;
前面我们简单的叙述了一下平衡因子的改变,这里具体情况分析一下.首先,我们是按照祖先来更新的,这一点无可置疑
if (cur == prev->_right)
prev->_bf++;
else
prev->_bf--;
也就说我们修改平衡因子,当我们子树的高度没变,也就是平衡因子为0的时候,停止更新.要是高度变了,变成了1那么肯定是右子树的高度变高了,这就需要我们继续往上面走,要是-1,是左树树高了,也要往上面走.这里最关键的就是-2和2,这个是我们要谈的重点,也是我们需要旋转的条件.
左单旋
假设右子树比左子树高了两个,而且还满足一定的情况,我们这就使用左单旋.我们先来分析一下情况.
至于如何旋转我们先暂时不谈,先来说几个具体的情况.
当 h = 0时
当 h = 1 时
当h = 2的时候这里的情况可就多了.
我们就不往后面分析了,这里说一下具体的用法,还是按照理论来进行旋转.
- 把subRL给成parent的右子树
- 把parent 给成subL的左子树
- 修改逻辑顺序和平衡因子
if (cur->_bf == 1 && parent->_bf == 2)
// 左旋
RotateL(parent);
void RotateL(Node* parent)
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if(subRL)
subRL->_praent = parent;
// 记录 祖父节点
Node* grandparent = parent->_praent;
parent->_praent = subR;
subR->_left = parent;
if (grandparent == nullptr)
// parent 就是 根节点
_pRoot = subR;
_pRoot->_praent = nullptr;
else
subR->_praent = grandparent;
// 在判断 原本 的父节点 和 祖父的关系
if (parent == grandparent->_left)
grandparent->_left = subL;
else
grandparent->_right = subL;
// 修改 平衡因子
subR->_bf = 0;
parent->_bf = 0;
我们先来把特殊的情况给分析一下,subRL可能为空,也就是h=0,这就造成subRL修改父节点的时候需要判断一下,还有就是你不感觉我们太顺风顺水了吗,你是如何确定parent一定是根节点的,我们前面的模型是一个子树的模型,不一定确保是完整的树.这个问题还是要判断的,否则就会出现问题.
右单旋
这个就比较简单了,我们这里直接上模型吧.右单旋就是右边高,这里需要向右边移动.
else if (cur->_bf == -1 && parent->_bf == -2)
// 右旋
RotateR(parent);
break;
void RotateR(Node* parent)
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
Node* grandparent = parent->_parent;
parent->_parent = subL;
subL->_right = parent;
if (grandparent == nullptr)
_pRoot = subL;
_pRoot->_parent = nullptr;
else
subL->_parent = grandparent;
if (grandparent->_left == parent)
grandparent->_left = subL;
else
grandparent->_right = subL;
// 更新平衡因子
subL->_bf = parent->_bf = 0;
我们先来测试一下单旋,也就是我们尽量插入有序的元素,这里就只需要单旋操作.但是这里我们需要注意,这里我们可不是能够确定我们的树是AVL树,只是看看我们代码是不是和逻辑符合.
void test1()
AVLTree<int, int> a;
for (int i = 0; i < 8; i++)
a.Insert(i+1);
cout << "层序遍历" << endl;
a.levelOrder();
cout << "中序遍历" << endl;
a.inorder();
左右双旋
现在我们开始一个比较复杂的情况,是双旋的问题.我们要是直接插入8,6,7.这就有意思了.看一下.你会发现简单的单旋是解决不了问题的.这里需要一点手段.
我们先来看一下模型吧,后面在讨论解决方案.这里就像是一个折线图.
这里我们也列举几个具体的情况.
这个时候我们就在想,这个应该如何旋转,这里需要一点变通.我们先来看大方针.
也就是说我们需要以subL做左单旋,后面以parent做右单旋.
else if (cur->_bf == 1 && parent->_bf == -2)
RotateLR(parent);
break;
void RotateLR(Node* parent)
RotateL(parent->_left);
RotateR(parent);
// 修改平衡因子
// ...
注意我们旋转是没有问题的,这里可以直接复用前面的,难的是需要修改平衡因子.那么这里面就存在下面的问题,看一看下面的方法
我们需要把这些节点给保存下来,而且还要讨论如何给这些节点修改平衡因子.
void RotateLR(Node* parent)
Node* subL = parent->_left;
Node* subLR = subL->_right;
RotateL(parent->_left);
RotateR(parent);
// 修改平衡因子
// ...
现在我们剩下的问题就是如何修改平衡因子了,我们知道subLR一定是0,但是subL和parent可就不一定了,这需要分情况讨论,那么我们讨论的依据是什么呢?这就有点意思了.我们根据苏subLR的平衡因子的值来讨论.不过我们需要先把它给记录下来,单旋的时候会把它给修改了.我们根据下面的图来修改就可以了.
void RotateLR(Node* parent)
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
// 修改平衡因子
if (bf == 0)
parent->_bf = 0;
subL->_bf = 0;
subLR->_bf = 0;
else if (bf == 1)
parent->_bf = 0;
subL->_bf = -1;
subLR->_bf = 0;
else if (bf == -1)
parent->_bf = 1;
subL->_bf = 0;
subLR->_bf = 0;
else
assert(false);
右左双旋
这个和上面的是一样的,也是折线,我们是需要右单旋加上左单旋.
看下解决思路,我这里就不具体的举例子了.
else if (cur->_bf == -1 && parent->_bf == 2)
RotateRL(parent);
break;
void RotateRL(Node* parent)
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(parent->_right);
RotateL(parent);
// 修改平衡因子
我么还是重点关注如何修改平衡因子,这个需要简单的分析一下,但是思路和上面都是一样的.
void RotateRL(Node* parent)
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(parent->_right);
RotateL(parent);
// 修改平衡因子
if (bf == 0)
subR->_bf = 0;
parent->_bf = 0;
subRL->_bf = 0;
else if(bf == 1)
subR->_bf = 0;
parent->_bf = -1;
subRL->_bf = 0;
else if (bf == -1)
subR->_bf = 1;
parent->_bf = 0;
subRL->_bf = 0;
else
assert(false);
我们这里也测试一下乱序的情况,避免代码出现错误.
void test2()
int arr[] = 1,4,3,7,5,8,1,10 ;
int sz = sizeof(arr) / sizeof(arr[0]);
AVLTree<int, int> a;
for (int i = 0; i < sz; i++)
a.Insert(arr[i]);
cout << "层序遍历" << endl;
a.levelOrder();
cout << "中序遍历" << endl;
a.inorder();
AVL树的判定
这里我们需要看看自己写的AVL树到底是不是正确的,这里直接上代码.
public:
bool isBalanceTree()
return _IsBalanceTree(_pRoot);
private:
int _height(Node* root)
if (root == nullptr)
return 0;
int lh = _height(root->_left);
int rh = _height(root->_right);
return lh > rh ? lh + 1 : rh + 1;
bool _IsBalanceTree(Node* root)
// 空树也是平衡二叉树
if (root == nullptr)
return true;
// 记录 左右节点的高度
int left = _height(root->_left);
int right = _height(root->_right);
int diff = left - right;
// 判断 平衡因子
if (abs(diff) >= 2)
cout << "平衡因子更新错误" << endl;
return false;
if (right - left != root->_bf)
cout << "节点平衡因子不符合实际" << endl;
return false;
return _IsBalanceTree(root以上是关于AVL树和红黑树的模拟实现的主要内容,如果未能解决你的问题,请参考以下文章