目录
1.AVL树介绍
2.AVL树如何进行平衡调整
2.1平衡因子
2.2AVL树的插入
2.3左单旋
2.4右单旋
2.5左右双旋
2.6右左双旋
2.8完整代码
3.测试用例
4.验证是否为AVL树
1.AVL树介绍
AVL树是map/set/multimap/multi/set等容器的一种底层结构,其本质就是一颗二叉搜索树。但因为二叉搜索树很容易退化成单支树,所以AVL树便是对二叉搜索树进行升级改造,使其平衡。
AVL进行平衡的方法为:向二叉搜索树插入节点,保证每个节点的左右子树高度差不超过1(可以包括1),如果确实达不到这种要求,就需要通过特定的算法来达到这样的要求。一颗AVL树可以是空树,也可以是具有以下性质的二叉搜索树:
1.每个节点的左右子树都是AVL树
2.每个节点的左右子树的高度差不超过1(可以包括1)
AVL树的搜索时间复杂度可以一直维持在O(log_2N)。
2.AVL树如何进行平衡调整
2.1平衡因子
平衡因子记录当前节点的左右子树的高度差。通过维护平衡因子,在设计AVL树时,可以很方便的判断出,什么情况下该使用什么样的调整算法。我们默认认为,左右子树高度差的计算公式为:右子树高度 - 左子树高度。
那么AVL树的节点可以这样定义:
template <class K>
struct AVLTreeNode
{
AVLTreeNode<K>* _left;
AVLTreeNode<K>* _right;
AVLTreeNode<K>* _parent; //指向父节点
K _key;
int _bf; //平衡因子
AVLTreeNode(const K& key)
:_left(nullptr), _right(nullptr), _parent(nullptr),_key(key), _bf(0)
{}
};
2.2AVL树的插入
节点进行插入的时候,我们需要维护平衡因子,还有维护指向父节点的指针。所以,我们的插入思路,可以这样写:
template<class K>
class AVLTree
{
typedef AVLTreeNode<K> Node;
public:
bool insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* parent = nullptr; //记录cur节点的上一个节点
Node* cur = _root;
while (cur)
{
if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else
{
// key和树中的某一键值相等,退出
return false;
}
}
// 找到了新节点的插入位置
cur = new Node(key);
if (key < parent->_key)
{
parent->_left = cur;
cur->_parent = parent; //注意维护指向父节点的指针
}
else if (key > parent->_key)
{
parent->_right = cur;
cur->_parent = parent;
}
// 现在需要维护平衡因子:右子树高度 - 左子树高度
while (parent) //parent节点不为空时,就需要对平衡因子维护
{
if (parent->_left == cur) //当新增节点在parent的左边时
{
parent->_bf--;
}
else if (parent->_right == cur) //新增节点在parent的右边时
{
parent->_bf++;
}
if (parent->_bf == 0) //更新平衡因子后,parent的平衡因子为0,就说明左右子树高度相等
//不需要进行处理
{
break;
}
else if (parent->_bf == 1 || parent->_bf == -1) //parent的左子树或右子树的高度增加了
//就需要往上更新平衡因子
{
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2) //parent的左右子树高度差不符合要求了
//需要根据平衡因子的关系来选用特定的算法调整
{
// ...调整算法
}
else
{
assert(false); //如果发生断言错误,则说明代码设计的有问题
}
}
return true;
}
private:
Node* _root = nullptr;
};
现在来详细解释一下更新平衡因子的算法:
同样的如上图所示,如果键值为9的节点作为根节点,那它势必是最后更新到的节点。所以在上面的代码中使用了while(parent)语句,当parent为空时就不进入更新平衡因子。
2.3左单旋
如果在较高右子树的右侧插入一个新节点,就需要使用左单旋算法:
void RotateL(Node* parent)
{
Node* cur = parent->_right;
Node* curL = cur->_left; //cur的左树
parent->_right = curL; //cur的左树变成parent的右树
if (curL)
{
curL->_parent = parent;
}
Node* oldParent = parent->_parent; //记录parent的父节点
parent->_parent = cur; //cur作为parent的父节点
cur->_left = parent; //parent作为cur的左树
if (oldParent == nullptr)
{
_root = cur; //直接让cur作为根节点(因为parent的旧父节点为空)
cur->_parent = nullptr;
}
else
{
if (oldParent->_left == parent)
{
oldParent->_left = cur;
cur->_parent = oldParent;
}
else if (oldParent->_right == parent)
{
oldParent->_right = cur;
cur->_parent = oldParent;
}
}
parent->_bf = cur->_bf = 0; //平衡因子都置为0(推理得出结论)
}
2.4右单旋
如果在较高左子树的左侧插入一个新节点,就需要使用右单旋算法:
void RotateR(Node* parent)
{
Node* cur = parent->_left;
Node* curR = cur->_right;
parent->_left = curR; //cur的右树作为parent的左树
if (curR)
{
curR->_parent = parent;
}
Node* oldParent = parent->_parent;
parent->_parent = cur;
cur->_right = parent; //parent作为cur的右树
if (oldParent == nullptr)
{
_root = cur;
cur->_parent = nullptr;
}
else
{
if (oldParent->_left == parent)
{
oldParent->_left = cur;
cur->_parent = oldParent;
}
else if (oldParent->_right == parent)
{
oldParent->_right = cur;
cur->_parent = oldParent;
}
}
parent->_bf = cur->_bf = 0;
}
2.5左右双旋
如果在较高左子树的右侧插入一个新节点,就需要使用左右双旋算法:
void RotateLR(Node* parent)
{
Node* cur = parent->_left;
Node* curR = cur->_right;
int bf = curR->_bf; //旋转之前记录一下cur的孩子节点的平衡因子
RotateL(cur);
RotateR(parent);
if (bf == 0) //如图所示h==0时
{
parent->_bf = cur->_bf = curR->_bf = 0;
}
else if (bf == -1) //如图所示h==1时第一种插入方式
{
parent->_bf = 1;
cur->_bf = 0;
curR->_bf = 0;
}
else if (bf == 1) //如图所示h==1时第二种插入方式
{
parent->_bf = 0;
cur->_bf = -1;
curR->_bf = 0;
}
}
2.6右左双旋
如果在较高右子树的左侧插入一个新节点,就需要使用右左双旋算法:
void RotateRL(Node* parent)
{
Node* cur = parent->_right;
Node* curL = cur->_left;
int bf = curL->_bf;
RotateR(cur);
RotateL(parent);
if (bf == 0) //h==0时
{
parent->_bf = cur->_bf = curL->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
cur->_bf = 1;
curL->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = -1;
cur->_bf = 0;
curL->_bf = 0;
}
}
2.8完整代码
template <class K>
struct AVLTreeNode
{
AVLTreeNode<K>* _left;
AVLTreeNode<K>* _right;
AVLTreeNode<K>* _parent; //指向父节点
K _key;
int _bf; //平衡因子
AVLTreeNode(const K& key)
:_left(nullptr), _right(nullptr), _parent(nullptr),_key(key), _bf(0)
{}
};
template<class K>
class AVLTree
{
typedef AVLTreeNode<K> Node;
public:
bool insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* parent = nullptr; //记录cur节点的上一个节点
Node* cur = _root;
while (cur)
{
if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else
{
// key和树中的某一键值相等,退出
return false;
}
}
// 找到了新节点的插入位置
cur = new Node(key);
if (key < parent->_key)
{
parent->_left = cur;
cur->_parent = parent; //注意维护指向父节点的指针
}
else if (key > parent->_key)
{
parent->_right = cur;
cur->_parent = parent;
}
// 现在需要维护平衡因子:右子树高度 - 左子树高度
while (parent) //parent节点不为空时,就需要对平衡因子维护
{
if (parent->_left == cur) //当新增节点在parent的左边时
{
parent->_bf--;
}
else if (parent->_right == cur) //新增节点在parent的右边时
{
parent->_bf++;
}
if (parent->_bf == 0) //更新平衡因子后,parent的平衡因子为0,就说明左右子树高度相等
//不需要进行处理
{
break;
}
else if (parent->_bf == 1 || parent->_bf == -1) //parent的左子树或右子树的高度增加了
//就需要往上更新平衡因子
{
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2) //parent的左右子树高度差不符合要求了
//需要根据平衡因子的关系来选用特定的算法调整
{
// ...调整算法
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 RotateL(Node* parent)
{
Node* cur = parent->_right;
Node* curL = cur->_left; //cur的左树
parent->_right = curL; //cur的左树变成parent的右树
if (curL)
{
curL->_parent = parent;
}
Node* oldParent = parent->_parent; //记录parent的父节点
parent->_parent = cur; //cur作为parent的父节点
cur->_left = parent; //parent作为cur的左树
if (oldParent == nullptr)
{
_root = cur; //直接让cur作为根节点(因为parent的旧父节点为空)
cur->_parent = nullptr;
}
else
{
if (oldParent->_left == parent)
{
oldParent->_left = cur;
cur->_parent = oldParent;
}
else if (oldParent->_right == parent)
{
oldParent->_right = cur;
cur->_parent = oldParent;
}
}
parent->_bf = cur->_bf = 0; //平衡因子都置为0(推理得出结论)
}
void RotateR(Node* parent)
{
Node* cur = parent->_left;
Node* curR = cur->_right;
parent->_left = curR; //cur的右树作为parent的左树
if (curR)
{
curR->_parent = parent;
}
Node* oldParent = parent->_parent;
parent->_parent = cur;
cur->_right = parent; //parent作为cur的右树
if (oldParent == nullptr)
{
_root = cur;
cur->_parent = nullptr;
}
else
{
if (oldParent->_left == parent)
{
oldParent->_left = cur;
cur->_parent = oldParent;
}
else if (oldParent->_right == parent)
{
oldParent->_right = cur;
cur->_parent = oldParent;
}
}
parent->_bf = cur->_bf = 0;
}
void RotateLR(Node* parent)
{
Node* cur = parent->_left;
Node* curR = cur->_right;
int bf = curR->_bf; //旋转之前记录一下cur的孩子节点的平衡因子
RotateL(cur);
RotateR(parent);
if (bf == 0) //如图所示h==0时
{
parent->_bf = cur->_bf = curR->_bf = 0;
}
else if (bf == -1) //如图所示h==1时第一种插入方式
{
parent->_bf = 1;
cur->_bf = 0;
curR->_bf = 0;
}
else if (bf == 1) //如图所示h==1时第二种插入方式
{
parent->_bf = 0;
cur->_bf = -1;
curR->_bf = 0;
}
}
void RotateRL(Node* parent)
{
Node* cur = parent->_right;
Node* curL = cur->_left;
int bf = curL->_bf;
RotateR(cur);
RotateL(parent);
if (bf == 0) //h==0时
{
parent->_bf = cur->_bf = curL->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
cur->_bf = 1;
curL->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = -1;
cur->_bf = 0;
curL->_bf = 0;
}
}
void level()
{
vector<vector<string>> vec;
if (_root == nullptr)
{
return;
}
queue<Node*> q;
q.push(_root);
while (!q.empty())
{
int size = q.size();
vector<string> tmp;
while (size--)
{
Node* front = q.front();
q.pop();
if (front)
{
tmp.push_back(to_string(front->_key));
}
else
{
tmp.push_back("nullptr");
}
if (front)
{
if (front->_left)
{
q.push(front->_left);
}
else
{
q.push(nullptr);
}
if (front->_right)
{
q.push(front->_right);
}
else
{
q.push(nullptr);
}
}
}
vec.push_back(tmp);
}
for (int i = 0; i < vec.size(); i++)
{
for (int j = 0; j < vec[i].size(); j++)
{
cout << vec[i][j] << " ";
}
cout << endl;
}
}
bool isAVLTree()
{
return isAVLTree(_root);
}
bool isAVLTree(Node* root)
{
if (root == nullptr)
{
return true;
}
int leftHeight = height(root->_left);
int rightHeight = height(root->_right);
if (root->_bf != (rightHeight - leftHeight))
{
cout << root->_key << "平衡因子出错:" << root->_bf << endl;
return false;
}
return abs(rightHeight - leftHeight) < 2
&& isAVLTree(root->_left)
&& isAVLTree(root->_right);
}
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;
}
private:
Node* _root = nullptr;
};
3.测试用例
二叉树非线性表,所以如果代码出现问题是很难看出问题的。所以这里给出两组测试用例,以便更早的发现问题。
常规场景:
{16,3,7,11,9,26,18,14,15}
特殊场景:
{4,2,6,1,3,5,15,7,16,14}
可以使用中序遍历或层序遍历来观察输出结果是否符合自己的预期。
4.验证是否为AVL树
前面一直强调,AVL树的每个节点左右子树的高度差不超过1。现在只需要写一个程序判断是否符合这个条件即可。
bool isAVLTree()
{
return isAVLTree(_root);
}
bool isAVLTree(Node* root)
{
if (root == nullptr)
{
return true;
}
int leftHeight = height(root->_left);
int rightHeight = height(root->_right);
if (root->_bf != (rightHeight - leftHeight))
{
cout << root->_key << "平衡因子出错:" << root->_bf << endl;
return false;
}
return abs(rightHeight - leftHeight) < 2
&& isAVLTree(root->_left)
&& isAVLTree(root->_right);
}
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;
}