【C++】AVL 树
目录
一. 概念
二. 实现
1. 插入
左单旋
右单旋
右左双旋
左右双旋
2. AVL 树的验证
三. 整体代码
一. 概念

控制树有多种方法,我们这里讲平衡因子(不是必须,也可以用其他方法实现 AVL树)
当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均 搜索长度
性质、判定:
1. 它的左右子树都是AVL树
2. 右子树高度 - 左子树高度(平衡因子)的绝对值不超过1(-1/0/1)
增删查改:高度次 O(logN)
二. 实现
AVLTree.h
template<class K, class V>
struct AVLTreeNode
{pair<K, V> _kv;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf; // balance factor 平衡因子AVLTreeNode(const pair<K, V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0){ }
};template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:bool Insert(const pair<K, V>& kv) { }private:Node* _root = nullptr;
};
1. 插入
二叉搜索树的比对、插入
bool Insert(const pair<K, V>& kv)
{if (_root == nullptr){_root = new Node(kv);return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);if (parent->_kv.first < kv.first)parent->_right = cur;elseparent->_left = cur;cur->_parent = parent;// 更新平衡因子、判断是否控制平衡// ...return true;
}
下面就要更新平衡因子、判断是否控制平衡(平衡因子本质是病,0 不用动,1 就要溯根,2 就得治)
1. 新增在左,parent 平衡因子 --
2. 新增在右,parent 平衡因子 ++
3. 更新后 parent 平衡因子 == 0,说明 parent 子树高度不变,不会再影响祖先,不用再继续沿着路径往上更新,直至 root(插入结束)
4. 更新后 parent 平衡因子 == 1 or -1,说明 parent 子树高度变化,会再影响祖先,但没有影响 AVL 树的大规则,继续沿着路径往上更新,直至 root(重复 12,再判断属于 3-6 的哪种情况)
5. 更新后 parent 平衡因子 == 2 or -2,说明 parent 子树高度变化且不平衡,要对 parent 子树旋转,让他平衡(插入结束)
6. 更新到根节点(插入结束)
旋转的时候要注意:
1. 保持是搜索树
2. 旋转后变成平衡树且降低这个子树的高度
bool Insert(const pair<K, V>& kv)
{// ... 插入节点// 更新平衡因子、判断是否控制平衡while (parent){if (parent->_left == cur)parent->_bf--;elseparent->_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 (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){RotateRL(parent); // 右左双旋}else if (parent->_bf == -2 && cur->_bf == 1){RotateLR(parent); // 左右双旋}break;}else{assert(false);}}return true;
}void RotateL(Node* parent) { }
左单旋
巧记:右边高就要向左压,且是直线,左单旋
旋转都是从头上绕过去,parent 把挨得近的 cur 的孩子节点叼走,自己当小弟,cur 当大哥

主要动了蓝圈 3 个节点,其他节点不变
核心操作:
parent->_right = cur->_left;
cur->_left = parent;
单有核心操作不够,此时 2 的父亲是 3;3 的父亲是 1;1 的父亲指向上面或是空
void RotateL(Node* parent)
{Node* cur = parent->_right;Node* curleft = cur->_left;Node* ppnode = parent->_parent;// 2个核心步骤parent->_right = curleft;cur->_left = parent; if (curleft) // 调整curleft父亲节点的指向{curleft->_parent = parent;}parent->_parent = cur; // 调整parent父亲节点的指向//cur->_parent = ppnode; 错:ppnode还要和cur建立关系// 调整cur父亲节点的指向if (_root == parent){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent)ppnode->_left = cur;elseppnode->_right = cur;cur->_parent = ppnode;}parent->_bf = cur->_bf = 0;
}

为什么 parent、cur 的平衡因子都要更新成 0? 看抽象图
插入之前,abc 是符合 AVL 规则的子树

右单旋
巧记:左边高就要向右压,且是直线,右单旋
旋转都是从头上绕过去,parent 把挨得近的 cur 的孩子节点叼走,自己当小弟,cur 当大哥

核心操作:
parent->_left = cur->_right;
cur->_right = parent;
void RotateR(Node* parent)
{Node* cur = parent->_left;Node* curright = cur->_right;Node* ppnode = parent->_parent;// 2个核心步骤parent->_left = curright;cur->_right = parent;parent->_parent = cur; // 调整parent父亲节点的指向if (curright) // 调整curright父亲节点的指向{curright->_parent = parent;}//cur->_parent = ppnode; 错:ppnode还要和cur建立关系// 调整cur父亲节点的指向if (_root == parent){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_right == parent)ppnode->_right = cur;elseppnode->_left = cur;cur->_parent = ppnode;}parent->_bf = cur->_bf = 0;
}
右左双旋
巧记:右边高就要向左压,本应左单旋,折线,就要先右单旋,再左单旋
最下面的节点,把左右孩子给离它近的 cur、parent,自己当大哥

先以 90 为旋转点,右单旋;再以 30 为旋转点,左单旋
RotateR(parent->_right);
RotateL(parent);
平衡因子分析:
h == 0:60 就是新插入的节点

h > 0:
1. 插入到 c 诱发右左双旋(上面的图)
2. 插入到 b 诱发右左双旋

void RotateRL(Node* parent)
{Node* cur = parent->_right;Node* curleft = cur->_left;int bf = curleft->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 0){cur->_bf = 0;curleft->_bf = 0;parent->_bf = 0;}else if (bf == 1){cur->_bf = 0;curleft->_bf = 0;parent->_bf = -1;}else if (bf == -1){cur->_bf = 1;curleft->_bf = 0;parent->_bf = 0;}else{assert(false);}
}
左右双旋
巧记:左边高就要向右压,本应右单旋,折线,就要先左单旋,再右单旋
最下面的节点,把左右孩子给离它近的 cur、parent,自己当大哥
h == 0:60 自己是新插入的节点

h > 0:
1. 插入到 b 引发左右旋转

2. 插入到 c 引发左右旋转

void RotateLR(Node* parent)
{Node* cur = parent->_left;Node* curright = cur->_right;int bf = curright->_bf;RotateL(parent->_left);RotateR(parent);if (bf == 0){cur->_bf = 0;curright->_bf = 0;parent->_bf = 0;}else if (bf == 1){cur->_bf = -1;curright->_bf = 0;parent->_bf = 0;}else if (bf == -1){cur->_bf = 0;curright->_bf = 0;parent->_bf = 1;}else{assert(false);}
}
2. AVL 树的验证
有时程序有 bug,且数据量大,我们不可能挨着用眼睛瞅
可以写程序辅助调试,让程序帮忙看,即:写程序判断是不是 AVL 树
int Height(Node* root)
{if (root == nullptr)return 0;int leftHeight = Height(root->_left);int rightHeight = Height(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}bool IsBalance()
{return IsBalance(_root);
}bool IsBalance(Node* root)
{if (root == nullptr)return true;int leftHeight = Height(root->_left); // 自己求左子树的高度int rightHeight = Height(root->_right); // 自己求右子树的高度if (rightHeight - leftHeight != root->_bf) // 自己验证平衡因子是否正确{cout << "平衡因子异常:" << root->_kv.first << "->" << root->_bf << endl;return false;}return abs(rightHeight - leftHeight) < 2 // 当前的平衡因子正确&& IsBalance(root->_left) // 并且左子树的平衡因子正确&& IsBalance(root->_right); // 并且右子树的平衡因子正确
} // 则说明整棵树是平衡树
Test.cpp
int main()
{//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };AVLTree<int, int> t;for (auto e : a){if (e == 15){int x = 0;}t.Insert(make_pair(e, e));cout << "Insert:" << e << "->" << t.IsBalance() << endl;}return 0;
}
如果锁定了插入哪个节点时导致平衡因子异常(出错)就写上 8-11 行,在第 10 行打断点,可以手动改条件断
int main()
{const int N = 10000000;vector<int> v;v.reserve(N);srand(time(0));for (size_t i = 0; i < N; i++){v.push_back(rand());}AVLTree<int, int> t;for (auto e : v){t.Insert(make_pair(e, e));//cout << "Insert:" << e << "->" << t.IsBalance() << endl;}cout << t.IsBalance() << endl;return 0;
}
三. 整体代码
AVLTree.h
template<class K, class V>
struct AVLTreeNode
{pair<K, V> _kv;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf; // balance factorAVLTreeNode(const pair<K, V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0){ }
};template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);if (parent->_kv.first < kv.first)parent->_right = cur;elseparent->_left = cur;cur->_parent = parent;// 更新平衡因子、判断是否控制平衡while (parent){if (parent->_left == cur)parent->_bf--;elseparent->_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 (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){RotateRL(parent); // 右左双旋}else if (parent->_bf == -2 && cur->_bf == 1){RotateLR(parent); // 左右双旋}break;}else{assert(false);}}return true;}void RotateL(Node* parent){Node* cur = parent->_right;Node* curleft = cur->_left;Node* ppnode = parent->_parent;// 2个核心步骤parent->_right = curleft;cur->_left = parent;if (curleft) // 调整curleft父亲节点的指向{curleft->_parent = parent;}parent->_parent = cur; //调整parent父亲节点的指向//cur->_parent = ppnode; 错:ppnode还要和cur建立关系//调整cur父亲节点的指向if (_root == parent){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent)ppnode->_left = cur;elseppnode->_right = cur;cur->_parent = ppnode;}parent->_bf = cur->_bf = 0;}void RotateR(Node* parent){Node* cur = parent->_left;Node* curright = cur->_right;Node* ppnode = parent->_parent;// 2个核心步骤parent->_left = curright;cur->_right = parent;parent->_parent = cur; // 调整parent父亲节点的指向if (curright) // 调整curright父亲节点的指向{curright->_parent = parent;}//cur->_parent = ppnode; 错:ppnode还要和cur建立关系// 调整cur父亲节点的指向if (_root == parent){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_right == parent)ppnode->_right = cur;elseppnode->_left = cur;cur->_parent = ppnode;}parent->_bf = cur->_bf = 0;}void RotateRL(Node* parent){Node* cur = parent->_right;Node* curleft = cur->_left;int bf = curleft->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 0){cur->_bf = 0;curleft->_bf = 0;parent->_bf = 0;}else if (bf == 1){cur->_bf = 0;curleft->_bf = 0;parent->_bf = -1;}else if (bf == -1){cur->_bf = 1;curleft->_bf = 0;parent->_bf = 0;}else{assert(false);}}void RotateLR(Node* parent){Node* cur = parent->_left;Node* curright = cur->_right;int bf = curright->_bf;RotateL(parent->_left);RotateR(parent);if (bf == 0){cur->_bf = 0;curright->_bf = 0;parent->_bf = 0;}else if (bf == 1){cur->_bf = -1;curright->_bf = 0;parent->_bf = 0;}else if (bf == -1){cur->_bf = 0;curright->_bf = 0;parent->_bf = 1;}else{assert(false);}}int Height(Node* root){if (root == nullptr)return 0;int leftHeight = Height(root->_left);int rightHeight = Height(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}bool IsBalance(){return IsBalance(_root);}bool IsBalance(Node* root){if (root == nullptr)return true;int leftHeight = Height(root->_left); // 自己求左子树的高度int rightHeight = Height(root->_right); // 自己求右子树的高度if (rightHeight - leftHeight != root->_bf) // 自己验证平衡因子是否正确{cout << "平衡因子异常:" << root->_kv.first << "->" << root->_bf << endl;return false;}return abs(rightHeight - leftHeight) < 2 // 当前的平衡因子正确&& IsBalance(root->_left) // 并且左子树的平衡因子正确&& IsBalance(root->_right); // 并且右子树的平衡因子正确} // 则说明整棵树是平衡树private:Node* _root = nullptr;
};
本篇的分享就到这里了,感谢观看,如果对你有帮助,别忘了点赞+收藏+关注。
小编会以自己学习过程中遇到的问题为素材,持续为您推送文章
