个人主页:敲上瘾-CSDN博客
个人专栏:游戏、数据结构、c语言基础、c++学习、算法
目录
一、什么是AVL树?
二、平衡因子
1、什么是平衡因子?
2、平衡因子如何更新?
三、单旋
1、左单旋
编辑
2、右单旋
四、双旋
1、左右双旋
2、右左双旋
五、AVL树检测
六、源码
一、什么是AVL树?
什么是AVL树?其实它就是一颗平衡的二叉搜索树,我们都知道一颗二叉搜索树在极端情况下会退化为单支(和链表同样的结构),那么它的查找效率就会变为O(N),AVL树的就是通过一些操作来防止这种退化,从而使查找效率保持在O(logN)。
如下同一组数据的两种二叉搜索树形态:
二、平衡因子
1、什么是平衡因子?
对一个AVL树操作的时候首先就是需要知道它是否平衡,所以可以在节点上多增加一个变量用来储存左右子树的高度差,这个数据我们就称之为平衡因子。(即平衡因子=左子树的高度-右子树的高度)平衡因子只是起到一个辅助的作用,也可用其他方式。
现在我们知道了高度差,那么高度差到达什么时候表示不平衡需要我们去调整呢?我们可以知道高度差为零的时候肯定是最好的,但⽽是有些情况是做不到⾼度差为0的。⽐如⼀棵树是2个结点,4个结点等情况下,⾼度差最好就是1。
所以我们就以平衡因子大于等于2或平衡因子小于等于-2为标准表示该树已经不平衡需要调整。
2、平衡因子如何更新?
对于一个AVL树(已平衡)我们插入一个数据实际上是往叶子节点插入,而无论如何插入都会影响它的父节点的平衡因子,父节点又会影响它的父节点,以此类推。这样在更新父节点的平衡因子时就会变得很麻烦。所以在设计节点的时候除了有指向左子树的指针和指向右子树的指针以外,还需要我们添加一个前驱指针指向父节点。
平衡因子具体如何更新?因为平衡因子=左子树高度-右子树高度。所以如果新元素插入在左子树则父节点的平衡因子加1,如果插入到右子树则父节点的平衡因子减1。然后继续更新该父节点(记为p)的父节点(记为pp),同样如果p是pp的左子树则pp的平衡因子加1,如果p是pp的右子树则平衡因子减1。以此类推往上更新。
平衡因子更新后一共有三种情况:
- 平衡因子更新为0:此时表示之前不平衡那一部分因为新节点的插入而被抵消,对整棵树的高度没有影响,所以就不会对它上一层的平衡因子造成影响,不用往上更新。
- 平衡因子更新为1或-1:此时表示新节点的插入使得整棵树的高度改变,会影响上一层,需要继续更新。
- 平衡因子更新为2或-2:此时表示新节点的插入使得整棵树的高度改变,并使这棵子树不平衡,需要通过旋转把它调整为平衡状态,而调整后整棵子树高度又回到原来状态,不会对上一层造成影响。不用往上更新。
bool Insert(const T& val)//插入元素
{
Node* cur = root;
Node* newNode = new Node(val);
//如果为空树则直接插入
if (root == nullptr)
{
root = newNode;
return true;
}
//查找插入位置
Node* parent = nullptr;
while (cur)
{
parent = cur;
if (val <= cur->data) cur = cur->left;
else cur = cur->right;
}
//插入操作
if (val <= parent->data) parent->left = newNode;
else parent->right = newNode;
newNode->prev = parent;
//更新平衡因子
cur = newNode;
while (parent != nullptr)
{
if (parent->left == cur) parent->bf--;
else parent->bf++;
if (parent->bf == 0) break;
else if (parent->bf == 1 || parent->bf == -1)
{
cur = parent;
parent = parent->prev;
}
else if (parent->bf == 2 || parent->bf == -2)
{
//不平衡,进行旋转
//......
}
}
return true;
}
三、单旋
1、左单旋
在这里为了兼容子树的多种状况和不失一般性,使用了a,b,c来抽象表示子树。如上图因为添加节点使得a的高度发生变化(但a子树依旧保持平衡),从而使得存放15这个节点(记为subR)的这棵个子树的高度改变(subR依旧平衡),从而影响上一层(记为parent),parent的平衡因子变为2,即左边过高,使得整棵树不平衡需要调整。
因为这里有这样一个规律,整颗b子树是比parent大的,所以可以把b子树接到parent右边,然后因为subR同样也比parent大但是要把高的子树提上去,所以把parent接到subR左边。
前驱指针更新
对于单旋的重难点还是在于前驱指针的更新,我们把它们分开单个分析:
- parent:parent的前驱直接指向subR即可,但是注意在这之前一定要先把原来parent的前驱记录起来(记为pparent)
- subR:首先将subR的前驱指向pparent,而pparent又分为两种情况:(1)、pparent为空,说明原来的parent是整棵树的根,所以现在需要把根更新为subR。(2)、pparent不为空,那么pparent同时也要指向subR,此时pparent的左子树还是右子树指向subR是个问题,我们可以判断parent是pparent的左孩子还是右孩子,如果是左孩子则pparent的左指向subR,同理如果是右孩子则pparent的右指向subR。
- subRL:对于subRL有两种情况:(1)、subRL为空,此时不用处理,(2)、subRL不为空,此时直接把subRL的前驱指针指向parent。
平衡因子更新
通过观察调整后的树,调整后涉及的节点它的左右子树高度都是一样的,所以对于单旋(包括右单旋)调整后的parent和subR平衡因子都需要更新为0,而在该过程中subRL的平衡因子并未受到影响,不用更新。
左单旋代码:
void RotateL(Node* parent)
{
Node* subR = parent->right;
Node* subRL = subR->left;
Node* pparent = parent->prev;
//修改子节点
parent->right = subRL;
subR->left = parent;
//修改父节点
parent->prev = subR;
if (subRL) subRL->prev = parent;
subR->prev = pparent;
if (pparent == nullptr) root = subR;//把根节点替换成subL
else
{
if (pparent->left == parent) pparent->left = subR;
else pparent->right = subR;
}
//修改平衡因子
parent->bf = 0;
subR->bf = 0;
}
2、右单旋
如果理解了左单旋那么右单旋就比较容易理解了,同样如果添加节点使得parent左边过高,则需要右旋。如图subL和subLR都比parent要小,但为了把左子树提上去,所以让parent接到subL的右边,subLR接到parent的左边。
前驱指针更新
- parent:parent的前驱直接指向subL即可,但是注意在这之前一定要先把原来parent的前驱记录起来(记为pparent)
- subL:首先将subL的前驱指向pparent,而pparent又分为两种情况:(1)、pparent为空,说明原来的parent是整棵树的根,所以现在需要把根更新为subL。(2)、pparent不为空,那么pparent同时也要指向subL,此时pparent的左子树还是右子树指向subL是个问题,我们可以判断parent是pparent的左孩子还是右孩子,如果是左孩子则pparent的左指向subL,同理如果是右孩子则pparent的右指向subL。
- subLR:对于subLR有两种情况:(1)、subLR为空,此时不用处理,(2)、subLR不为空,此时直接把subLR的前驱指针指向parent。
平衡因子更新
通过观察调整后的树,调整后涉及的节点它的左右子树高度都是一样的,所以对于单旋调整后的parent和subL平衡因子都需要更新为0,而在该过程中subLR的平衡因子并未受到影响,不用更新。
右单旋代码:
void RotateR(Node* parent)
{
Node* subL = parent->left;
Node* subLR = subL->right;
Node* pparent = parent->prev;
parent->left = subLR;
subL->right = parent;
//修改前驱
parent->prev = subL;
if (subLR) subLR->prev = parent;
subL->prev = pparent;
if (pparent == nullptr) root = subL;
else
{
if (pparent->left == parent) pparent->left = subL;
else pparent->right = subL;
}
parent->bf = 0;
subL->bf = 0;
}
四、双旋
1、左右双旋
对于该场景我们可以发现无论让它左旋还是右旋都无法到达平衡状态,它与单旋的区别是parent和subL的平衡因子是一正一负,也就是说它并不是单纯的左边高或单纯的右边高,如上图对于subL来说是左边高,对于parent来说是右边高。
不过也好办,只需要先以subL为旋转点进行左旋,再以parent为旋转点进行右旋,这个可以直接复用上面单旋的代码。如下:
void RotateLR(Node* prev)
{
RotateL(prev->left);
RotateR(prev);
}
不过有一个细节,平衡因子如何改变?我们就需要对b子树展开进行分析,如下:
左右双旋:
以场景一为例动画展示:第一部以subL为旋转点进行左旋,第二部以parent为旋转点进行右旋。
通过观察很容易发现,如果原先subLR平衡因子为-1,则旋转后parent、subL、subLR平衡因子分别为1,0,0。如果原先subLR平衡因子为1,则旋转后parent、subL、subLR平衡因子分别为0,-1,0。对于这个特点我们直接在旋前进行判断直接修改就行。
特殊场景:
对于该场景各节点的平衡因子反而是不需要特殊处理的,单旋过程已经处理过了。
2、右左双旋
右左双旋相对于左右双旋就是照葫芦画瓢,这里就不做过多的讲解。
旋转代码:
void RotateRL(Node* prev)
{
RotateR(prev->right);
RotateL(prev);
}
同样如果原先subRL平衡因子为-1,则旋转后parent、subR、subRL平衡因子分别为0,1,0。如果原先subLR平衡因子为1,则旋转后parent、subR、subRL平衡因子分别为-1,0,0。对于这个点我们直接在旋前进行判断直接修改就行。
该场景的平衡因子处理和单旋处理相同,不用做特殊处理。
五、AVL树检测
当我们写好一个AVL树后然后判断它是否平衡是个问题。注意这里我们不能直接使用平衡因子去判断,平衡因子只是我们用来创建AVL树的一个工具,要看一颗AVL树是否真正平衡则需要去看它的高度差,如果任意一颗子树的高度差大于等于2那么这个树就不平衡。
代码如下:
int Height(Node* root)//计算高度
{
if (root == nullptr) return 0;
int l = Height(root->left);
int r = Height(root->right);
return l > r ? l : r;
}
bool _IsAVLTree(Node* root)//判断是否平衡
{
if (root == nullptr) return true;
int LHeight = Height(root->left);
int RHeight = Height(root->right);
if (LHeight - RHeight >= 2 || LHeight - RHeight <= -2) return false;
return _IsAVLTree(root->left) && _IsAVLTree(root->right);
}
六、源码
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<assert.h>
using namespace std;
template<typename T>
struct AVLTreeNode
{
AVLTreeNode(const T& val = T())
:left(nullptr)
, right(nullptr)
, prev(nullptr)
, data(val)
, bf(0) {}
AVLTreeNode<T>* left;
AVLTreeNode<T>* right;
AVLTreeNode<T>* prev;
T data;
int bf;
};
template<typename T>
class AVLTree
{
typedef AVLTreeNode<T> Node;
public:
bool Insert(const T& val)
{
Node* cur = root;
Node* newNode = new Node(val);
if (root == nullptr)
{
root = newNode;
return true;
}
Node* parent = nullptr;
while (cur)
{
parent = cur;
if (val <= cur->data) cur = cur->left;
else cur = cur->right;
}
if (val <= parent->data) parent->left = newNode;
else parent->right = newNode;
newNode->prev = parent;
cur = newNode;
while (parent != nullptr)
{
if (parent->left == cur) parent->bf--;
else parent->bf++;
if (parent->bf == 0) break;
else if (parent->bf == 1 || parent->bf == -1)
{
cur = parent;
parent = parent->prev;
}
else if (parent->bf == 2 || parent->bf == -2)
{
//旋转
if (parent->bf == -2 && cur->bf == -1)
RotateR(parent);
else if (parent->bf == 2 && cur->bf == 1)
RotateL(parent);
else if (parent->bf == -2 && cur->bf == 1)
{
if (cur->right->bf == -1) parent->bf = 1;
else cur->bf = -1;
RotateLR(parent);
}
else if (parent->bf == 2 && cur->bf == -1)
{
if (cur->left->bf == -1) cur->bf = 1;
else parent->bf = -1;
RotateRL(parent);
}
else assert(false);
return true;
}
else
{
assert(0);
}
}
return false;
}
void RotateL(Node* parent)
{
Node* subR = parent->right;
Node* subRL = subR->left;
Node* pparent = parent->prev;
//修改子节点
parent->right = subRL;
subR->left = parent;
//修改父节点
parent->prev = subR;
if (subRL) subRL->prev = parent;
subR->prev = pparent;
if (pparent == nullptr) root = subR;//把根节点替换成subL
else
{
if (pparent->left == parent) pparent->left = subR;
else pparent->right = subR;
}
//修改平衡因子
parent->bf = 0;
subR->bf = 0;
}
void RotateR(Node* parent)
{
Node* subL = parent->left;
Node* subLR = subL->right;
Node* pparent = parent->prev;
parent->left = subLR;
subL->right = parent;
//修改前驱
parent->prev = subL;
if (subLR) subLR->prev = parent;
subL->prev = pparent;
if (pparent == nullptr) root = subL;
else
{
if (pparent->left == parent) pparent->left = subL;
else pparent->right = subL;
}
parent->bf = 0;
subL->bf = 0;
}
void RotateLR(Node* parent)
{
RotateL(parent->left);
RotateR(parent);
}
void RotateRL(Node* parent)
{
RotateR(parent->right);
RotateL(parent);
}
int Height(Node* root)
{
if (root == nullptr) return 0;
int l = Height(root->left);
int r = Height(root->right);
return l > r ? l : r;
}
bool IsAVLTree()
{
return _IsAVLTree(root);
}
bool _IsAVLTree(Node* root)
{
if (root == nullptr) return true;
int LHeight = Height(root->left);
int RHeight = Height(root->right);
if (LHeight - RHeight >= 2 || LHeight - RHeight <= -2) return false;
return _IsAVLTree(root->left) && _IsAVLTree(root->right);
}
private:
Node* root = nullptr;
};