怎么做网站推广多少钱网络营销促销方案
目录
第一节:AVL树的特征
第二节:实现思路
2-1.插入
2-1-1.右单旋
2-1-2.左单旋
2-1-3.左右双旋
2-1-4.右左双旋
2-1-5.总结
2-2.删除
第三节:代码实现
3-1.Node类
3-2.AVLTree类
3-2-1.Insert函数
3-2-2.Height函数
3-2-3.Balance函数
3-2-4.其他函数
3-2-5.clear函数
第四节:测试
第五节:优化方案
gitee:AVL树 · 转调/C++ - 码云 - 开源中国
第六章:总结
下期预告:
第一节:AVL树的特征
如果一个搜索二叉树的结构如下:
那么当我想找a的时候,时间复杂度就从 log₂n 退化成 n 了。
究其原因,搜索二叉树没有一个机制让树的整体高度变得更低。
所以就出现了AVL树,AVL树通过保证每个节点的左右子树高度差不超过[-1,1],保证整棵树的平衡。
将左右子树高度差抽象成平衡因子,平衡因子=右子树高度-左子树高度,每个节点都要维护自己的平衡因子不超过1。
第二节:实现思路
2-1.插入
1.首先按照搜索二叉树的规则进行插入
2.插入后更新其父亲节点的平衡因子
a.插入父亲节点的左子树,父亲节点平衡因子--
b.插入父亲节点的右子树,父亲节点平衡因子++
3.如果父亲节点平衡因子是-1或者1,说明父亲所在子树的高度改变了,继续更新父亲的父亲节点的平衡因子,若父亲的父亲节点的平衡因子也为-1或者1,继续向上更新,直到平衡因子不为-1或者1;
如果某个祖先节点的平衡因子更新后为0,说明祖先节点所在子树高度不变了,对祖先之上的节点也没有影响了,不再往上更新;
如果某个祖先节点的平衡因子为-2或者2,就需要进行旋转处理了:
2-1-1.右单旋
当前节点平衡因子为-2 && 其左孩子的平衡因子为-1 进行右单旋
用长方形表示子树高度,b、c的高度都为h,a的高度为h+1,红色方块为节点新增位置,蓝色字体为节点的平衡因子:
60的平衡因子为-2,并且其左孩子30的平衡因子为-1,进行右单旋:
具体的方法是将60的左孩子变成30的右孩子,30的右孩子被"抢走了",就把60变成30的右孩子,最后把60的父亲变成30的父亲。
这样做之后30和60的平衡因子都变成了0,就不需要再向上更新了。
可以发现30向右旋转把60顶替了,60向右旋转屈居30,a、b、c从左向右的顺序依然不变。
2-1-2.左单旋
当前节点平衡因子为+2 && 其右孩子的平衡因子为+1
30的平衡因子为+2,并且其右孩子60的平衡因子为+1,进行左单旋:
这样做之后30和60的平衡因子都变成了0,就不需要再向上更新了。
可以发现60向左旋转把30顶替了,30向左旋转屈居30,a、b、c从左向右的顺序依然不变。
2-1-3.左右双旋
当前节点平衡因子为-2 && 其左孩子的平衡因子为+1时进行左右双旋。
左右双旋有三种不同的情况,它们的旋转逻辑是一样的,但是最后的平衡因子不同。
旋转逻辑:
先不看它们的平衡因子,只看节点和子树的交换关系,此时a=h,d=h,b和c有一个等于h-1,另一个等于h,才能保证90的平衡因子为-2,30的平衡因子为1。
其实就是孙子变成了它爸爸和爷爷共同的父亲,然后仍然保持a、b、c、d的左右顺序
(1)如果60自己就是新增的节点
3个节点的平衡因子都为0,不再向上更新。
为了便于理解,我使用绿色字母表示新增节点后a、b、c、d的高度。
(2)如果60的左子树新增节点
此时60的平衡因子为0,不再向上更新,30的平衡因子为0,90的平衡因子为1
(3)如果60的右子树新增节点
此时60的平衡因子为0,不再向上更新,30的平衡因子为-1,90的平衡因子为0
2-1-4.右左双旋
当前节点平衡因子为+2 && 其右孩子的平衡因子为-1时进行左右双旋。
左右双旋也有三种不同的情况,它们的旋转逻辑是一样的,但是最后的平衡因子不同。
旋转逻辑:
![]()
也是孙子变成了它爸爸和爷爷共同的父亲,然后仍然保持a、b、c、d的左右顺序 ,这和左右双旋的结果一致。
所以如果是60的左子树b新增节点,旋转后,30、60、90的平衡因子分别是0、0、1;
如果是60的右子树c新增节点,旋转后,30、60、90的平衡因子分别是-1、0、0;
如果60本身是新增的节点,旋转后,30、60、90的平衡因子都是0。
2-1-5.总结
只要进行旋转后,"辈分"最高的节点的平衡因子都变成了0,说明只要进行过旋转,就不需要再往上更新了。
第三节:代码实现
将以上思路整理成代码。
3-1.Node类
首先创建一个节点类:
它除了节点的连接和保存的值之外,还需要一个变量_bf来保存平衡因子。
template<class T> class Node { public:Node<T>* _left = nullptr;Node<T>* _right = nullptr;Node<T>* _parent = nullptr;T _val;short _bf = 0; };
3-2.AVLTree类
它是AVL树的具体实现。
class AVLTree
{
private:Node<T>* _root = nullptr; // 保存根节点
};
3-2-1.Insert函数
首先完成插入函数,它传入一个值,然后按照搜索二叉树的思路插入节点:
void Insert(const T& val){// 如果根节点为空,赋值根节点即可if (_root == nullptr){_root = new Node<T>;_root->_val = val;return;}Node<T>* cur = _root;Node<T>* parent = nullptr;while (cur){if (cur->_val < val){parent = cur;cur = cur->_right;}else if (cur->_val > val){parent = cur;cur = cur->_left;}}cur = new Node<T>;cur->_val = val;if (parent->_val < val){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;// 更新祖先节点的平衡因子while (parent){if (cur == parent->_left){parent->_bf--;}else{parent->_bf++;}// 继续更新if (abs(parent->_bf) == 1){cur = parent;parent = parent->_parent;continue;}// 不再更新if (parent->_bf == 0){break;}// 如果失衡,进行旋转if (abs(parent->_bf) > 1){Rotate(parent);break;}}}
3-2-2.Height函数
它获取某个节点的高度,获得某个节点左右孩子的高度后,就可以计算_bf:
size_t _Height(Node<T>* root){if (root == nullptr) return 0;return std::max(_Height(root->_left), _Height(root->_right)) + 1;}size_t Height(Node<T>* root){return _Height(root);}
3-2-3.Balance函数
AVL树的核心函数,它用来更新节点的平衡因子:
void Rotate(Node<T>* cur) {// 进行旋转处理,旋转完也不需要继续更新了if (cur->_bf == -2 && cur->_left->_bf == -1) // 右单旋{Node<T>* P = cur; // 父亲Node<T>* S = P->_left; // 儿子P->_left = S->_right;if (S->_right)S->_right->_parent = P;S->_right = P;if (P != _root){if (P == P->_parent->_left){P->_parent->_left = S;}else if (P == P->_parent->_right){P->_parent->_right = S;}}else{_root = S;}S->_parent = P->_parent;P->_parent = S;P->_bf = 0;S->_bf = 0;}else if (cur->_bf == 2 && cur->_right->_bf == 1) // 左单旋{Node<T>* P = cur;Node<T>* S = P->_right;P->_right = S->_left;if (S->_left)S->_left->_parent = P;S->_left = P;if (P != _root){if (P == P->_parent->_left){P->_parent->_left = S;}else if (P == P->_parent->_right){P->_parent->_right = S;}}else{_root = S;}S->_parent = P->_parent;P->_parent = S;P->_bf = 0;S->_bf = 0;}else if (cur->_bf == -2 && cur->_left->_bf == 1) // 左右双旋{Node<T>* P = cur; // 父亲Node<T>* S = P->_left; // 儿子Node<T>* G = S->_right;// 孙子P->_left = G->_right;if (G->_right)G->_right->_parent = P;G->_right = P;S->_right = G->_left;if (G->_left)G->_left->_parent = S;G->_left = S;if (P != _root){if (P == P->_parent->_left){P->_parent->_left = G;}else if (P == P->_parent->_right){P->_parent->_right = G;}}else{_root = G;}G->_parent = P->_parent;P->_parent = G;S->_parent = G;if (G->_bf == 1){S->_bf = -1;P->_bf = 0;}else if (G->_bf == -1){S->_bf = 0;P->_bf = 1;}else if (G->_bf == 0){S->_bf = P->_bf = 0;}G->_bf = 0;}else if (cur->_bf == 2 && cur->_right->_bf == -1) // 右左双旋{Node<T>* P = cur;Node<T>* S = P->_right;Node<T>* G = S->_left;P->_right = G->_left;if (G->_left)G->_left->_parent = P;G->_left = P;S->_left = G->_right;if (G->_right)G->_right->_parent = S;G->_right = S;if (P != _root){if (P == P->_parent->_left){P->_parent->_left = G;}else if (P == P->_parent->_right){P->_parent->_right = G;}}else{_root = G;}G->_parent = P->_parent;P->_parent = G;S->_parent = G;if (G->_bf == 1){S->_bf = 0;P->_bf = -1;}else if (G->_bf == -1){S->_bf = 1;P->_bf = 0;}else if (G->_bf == 0){S->_bf = P->_bf = 0;}G->_bf = 0;} }
3-2-4.其他函数
Print 和 IsBalance 用来按升序打印内容和判断每个节点是否都平衡:
bool IsBalance(){return _IsBalance(_root);}void Print(){_Print(_root);}bool _IsBalance(Node<T>* root){if (root == nullptr) return true;if (abs(root->_bf) > 1) return false;return _IsBalance(root->_left) && _IsBalance(root->_right);}void _Print(Node<T>* root){if (root == nullptr) return;_Print(root->_left);std::cout << root->_val << " ";_Print(root->_right);}
3-2-5.clear函数
因为节点都是new出来的,所以使用清理函数释放空间,析构函数也要调用这个函数:
void _clear(Node<T>* root){if (root == nullptr) return;_clear(root->_left);_clear(root->_right);delete root;}void clear(){_clear(_root);}~AVLTree(){clear();}
第四节:测试
插入多个随机数,然后调用 Print 和 IsBalance。
#include <stdio.h> #include <stdlib.h> #include <time.h>#include "AVLTree.hpp"int main() {zd::AVLTree<int> tree;srand(time(NULL));int i = 100;while (i--){int x = rand();tree.Insert(x);}tree.Print();if (tree.IsBalance()){printf("true\n");}else{printf("false\n");}return 0; }
观察打印结果和最后是否为true即可。
第五节:优化方案
实际上左右双旋和右左双旋就是左单旋和右单旋的组合,所以可以将左单旋、右单旋封装成函数,供左右单旋和右左单旋调用。
但是封装的时候G的_bf需要提前保存,因为调用两次单旋之后,G的_bf就改变了:
void RotateR(Node<T>* cur) // 右单旋{Node<T>* P = cur;Node<T>* S = P->_left;P->_left = S->_right;if (S->_right)S->_right->_parent = P;S->_right = P;if (P != _root){if (P == P->_parent->_left){P->_parent->_left = S;}else if (P == P->_parent->_right){P->_parent->_right = S;}}else{_root = S;}S->_parent = P->_parent;P->_parent = S;P->_bf = 0;S->_bf = 0;}void RotateL(Node<T>* cur) // 左单旋{Node<T>* P = cur;Node<T>* S = P->_right;P->_right = S->_left;if (S->_left)S->_left->_parent = P;S->_left = P;if (P != _root){if (P == P->_parent->_left){P->_parent->_left = S;}else if (P == P->_parent->_right){P->_parent->_right = S;}}else{_root = S;}S->_parent = P->_parent;P->_parent = S;P->_bf = 0;S->_bf = 0;}void RotateLR(Node<T>* cur) // 左右双旋{Node<T>* P = cur;Node<T>* S = P->_left;Node<T>* G = S->_right;short bf = G->_bf;RotateL(S);RotateR(P);if (bf == 1){S->_bf = -1;P->_bf = 0;}else if (bf == -1){S->_bf = 0;P->_bf = 1;}else if (bf == 0){S->_bf = P->_bf = 0;}G->_bf = 0;}void RotateRL(Node<T>* cur) // 右左双旋{Node<T>* P = cur;Node<T>* S = P->_right;Node<T>* G = S->_left;short bf = G->_bf;RotateR(S);RotateL(P);if (bf == 1){S->_bf = 0;P->_bf = -1;}else if (bf == -1){S->_bf = 1;P->_bf = 0;}else if (bf == 0){S->_bf = P->_bf = 0;}G->_bf = 0;}
用以上接口替换Rotate中的对应代码即可。
Gitee:AVL树 · 转调/C++ - 码云 - 开源中国
第六章:总结
单旋时,向左会被旋转到向右,向右会被旋转到向左,但是a、b、c的顺序不变
双旋时,两种双旋的结果一样,a、b、c、d的顺序也不变,而且双旋可以拆分成两次单旋:S先旋、P后旋。
负二左负右单旋,反之正一左右旋;正二右正左单旋,反之负一右左旋;两个双旋子先动;a、b、c、d顺序齐。
下期预告:
第十三章将介绍另外一种搜索二叉树——红黑树。