数据结构【AVL树模拟实现】
创始人
2024-01-28 14:20:41
0

目录

AVL树概念

AVL树结构

insert

AVL树的旋转

新节点插入较高右子树的右侧---右右:左单旋

新节点插入较高左子树的左侧---左左:右单旋

新节点插入较高左子树的右侧---左右:先左单旋再右单旋

新节点插入较高右子树的左侧---右左:先右单旋再左单旋

insert实现

测试二叉树是否为AVL树


AVL树概念

当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1,超过1需要对树中的结点进行调整。即可降低树的高度,从而减少平均搜索长度。

一棵AVL树性质:

1.它的左右子树都是AVL(任意一个子树左右高度差都不超过1)

2.左右子树高度之差(简称平衡因子)的绝对值不超过1(平衡因子是其中一种实现方式,判断平衡因子即可判断是否为AVL树)

3.平衡因子 = 右子树高度 - 左子树高度

4.AVL树是一棵绝对平衡的二叉搜索树,查询的时间复杂度log2(N)。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时, 有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。

AVL树结构

引入平衡因子,需要三岔链,原因在于新插入节点还需保持AVL树的特性,新插入节点后平衡因子需要改变(更新新插入节点祖先路径)

	templatestruct AVLTreeNode{AVLTreeNode* _left;AVLTreeNode* _right;AVLTreeNode* _parent;int _bf;//平衡因子pair _kv;};templateclass AVLTree{public:typedef AVLTreeNode Node;private:Node* _root;};

insert

插入后AVL树有可能导致不平衡,此时需要沿着parent更新平衡因子

更新平衡因子规则:

1.如果此时是在parent的右,parent平衡因子++

2.如果此时是在parent的左,parent平衡因子--

3.更新后parent平衡因子如果为0,说明之前是1--/-1++,代表左右子树一边高一边低。插入后变为0,表示两边子树一样高。此时parent则不用继续往上更新(左右子树平衡)

4.更新后parent平衡因子如果为1/-1,说明原来parent平衡因子只可能是0,0代表左右子树高度相等。更新完后为1/-1,代表左右子树一边高一边低,表示parent的高度也改变了,继续往上更新平衡因子(子树高度改变影响父亲)

5.更新后praent平衡因子如果为2/-2,此时parent所在的子树需要旋转处理

insert的基本逻辑,不包括旋转,旋转下面总结

		bool insert(const pair& 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->left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(kv);if (parent->_kv.first > kv.first){parent->_left = cur;}else if (parent->_kv.first < kv.first){parent->_right = cur;}cur->_parent = parent;//while控制平衡因子,最坏的情况下,更新到根节点while (parent){if (parent->_left == cur){parent->_bf--;}else if (parent->_right == cur){parent->_bf++;}if (parent->_bf == 1 || parent->_bf == -1){parent = parent->_parent;cur = cur->_parent;//沿着三岔链往上走}else if (parent->_bf == 0){break;}else if(parent->_bf == 2 || parent->_bf == -2)//parent所在子树不平衡,开始旋转{}else{assert(false);//直接报错,插入之前平衡因子已经出问题}}return true;}

AVL树的旋转

旋转规则:

1.如果有不平衡的情况,一定在parent(parent和cur是沿着三岔链更新上去的)

2.旋转成平衡二叉搜索树树

3.旋转后还需要继续保持平衡二叉搜索树的特性

4.具象图有无限种情况,此时我们需要抽象总结归纳avl树旋转的场景和情况

新节点插入较高右子树的右侧---右右:左单旋

抽象图:h可以是任意高度(h>=0)

此处抽象图要求c本身需要平衡,否则插入可能导致c自身旋转

新节点插入较高右子树的右侧c中(无论h有多高,  新增的节点在c的左or右),插入在c中结果都是parent引发的旋转(parent->bf == 2)

旋转方式:让b作为parent的右子树(b中所有节点都比parent大,没有违反规则);让parent这颗树(a+b)作为tmp处的左子树(所有节点都小于tmp,没有违反规则),此时左子树是h+1,右子树是h+1,整棵树保持高度平衡

此时旋转就有三个位置需要标记:parent位置,parent->right称为subR,parent->right->left称为subRL

此时有以下情况需要处理:

1.注意parent旋转后,有可能是根节点,有可能是整棵树中的一个子树,此时还需要保存parent->_parent,来让旋转后的subR指向_parent

2.如果parent->_parent为nullptr,则代表旋转后subR变成根节点

3.需要改变六个指针的位置

4.一次插入最多更新高度次,在高度次中,只会出现一次旋转更新。如果在while更新平衡因子过程中出现旋转,旋转完成后整棵树即可保持平衡,插入之前树的高度是h+2,插入时高度变成h+3,旋转完成后高度继续变为h+2,对上一层没有影响,直接break跳出旋转即可

5.只有parent和subR的平衡因子受到影响(a,b不变),旋转完后parent和subR平衡因子变为0

	void RotaL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;Node* pparent = parent->_parent;parent->_right = subRL;//1if (subRL != nullptr){subRL->_parent = parent;//2} subR->_left = parent;//3subR->_parent = pparent;//4parent->_parent = subR;//5if (pparent){if (pparent->_left == parent){pparent->_left = subR;//6}else{pparent->_right = subR;//6}}else//root == nullptr,此时subR为根{_root = subR;//6}}

新节点插入较高左子树的左侧---左左:右单旋

新节点插入较高左子树的左侧a中(无论h有多高,  新增的节点在a的左or右),插入在a中结果都是parent引发的旋转(parent->bf == -2) 

旋转方式:让b作为parent的左子树(b中所有节点都比parent小,没有违反规则);让parent这棵树 (b+c)作为tmp处的右子树(所有节点都大于tmp,没有违反规则),此时左子树是h+1,右子树是h+1,整棵树保持高度平衡

此时旋转就有三个位置需要修改标记:parent位置,parent->left称为subL,parent->left->right称为subLR

此时有以下情况需要处理:

1.注意parent旋转后,有可能是根节点,有可能是整棵树中的一个子树,此时还需要保存parent->_parent,来让旋转后的subR指向_parent

2.如果parent->_parent为nullptr,则代表旋转后subR变成根节点

3.需要改变六个指针的位置

4.一次插入最多更新高度次,在高度次中,只会出现一次旋转更新。如果在while更新平衡因子过程中出现旋转,旋转完成后整棵树即可保持平衡,插入之前树的高度是h+2,插入时高度变成h+3,旋转完成后高度继续变为h+2,对上一层没有影响,直接break跳出旋转即可

5.只有parent和subR的平衡因子受到影响(b,c不变),旋转完后parent和subR平衡因子变为0

		void RotaR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;Node* pparent = parent->_parent;parent->_left = subLR;//1if (subLR)subLR->_parent = parent;//2subL->_right = parent;//3parent->_parent = subL;//4subL->_parent = pparent;//5if (pparent){if (pparent->_left == parent){pparent->_left = subL;//6}else{pparent->_right = subL;//6}}else{_root = subL;//6}parent->_bf = subL->_bf = 0;}

新节点插入较高左子树的右侧---左右:先左单旋再右单旋

如果此时新插入的节点在新节点插入较高左子树的右侧,不能使用右单旋

只使用右单旋解决:无法解决问题(从左子树平衡因子2变成右子树平衡因子2,旋转呈对称)

旋转思路:先对30进行左单旋,再对60进行右单旋

旋转的情况是相同的,只是平衡因子会变化

旋转的过程不变,但是平衡因子的更新却不同,原因在于可能插入在b中,也可能插入在c中

		void RotaLR(Node* parent){RotaL(parent->_left);RotaR(parent);//parent每次都在-2处//处理平衡因子,有三种情况}

在c中插入时,90平衡因子变为0,60变为0,30变为-1

还有一种极端情况,当h为0时,三个平衡因子都为0(60就是新增的节点)

 

插入在左子树的右侧不同位置,三个位置平衡因子不同

看最开始插入数据,60的平衡因子会根据插入的不同位置,平衡因子也右不同的变化

当60(subLR)平衡因子为-1时,插入在b中;平衡因子为1时,插入在c中;为0时,h高度为0

 

插入不同位置,平衡因子变化也不同,平衡因子最后变化看插入在哪里(也就是插入时subLR即可),h==0时处平衡因子即为0,直接推导出不画图

		void RotaLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotaL(parent->_left);RotaR(parent);//parent每次都在-2处//处理平衡因子,有三种情况if (bf == 1)//新增在{parent->_bf = 0;subL->_bf = -1;}else if (bf == -1)//新增在{parent->_bf = 1;subL->_bf = 0;}else if (bf == 0)//h == 0{parent->_bf = 0;subL->_bf = 0;}else{assert(false);}subLR->_bf = 0;}

新节点插入较高右子树的左侧---右左:先右单旋再左单旋

看60的bf确定平衡因子是左/右种的哪一种情况,h==0时平衡因子直接为0不用画了

 

随机一种右左双旋的情况,旋转情况一样,只是平衡因子不同,看下图配合

 

根左右双旋同理,分三种情况的平衡

		void RotaRL(Node* parent){Node* subR = parent->_right;Node* subRL = subL->_left;int bf = subRL->_bf;RotaL(parent->_right);RotaR(parent);//parent每次都在-2处//处理平衡因子,有三种情况if (bf == 1)//新增在c{parent->_bf = -1;subR->_bf = 0;}else if (bf == -1)//新增在b{parent->_bf = 0;subR->_bf = 1;}else if (bf == 0)//h == 0{parent->_bf = 0;subR->_bf = 0;}else{assert(false);}subRL->_bf = 0;}

 

 

insert实现

		bool insert(const pair& 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->left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(kv);if (parent->_kv.first > kv.first){parent->_left = cur;}else if (parent->_kv.first < kv.first){parent->_right = cur;}cur->_parent = parent;//while控制平衡因子,最坏的情况下,更新到根节点while (parent){if (parent->_left == cur){parent->_bf--;}else if (parent->_right == cur){parent->_bf++;}if (parent->_bf == 1 || parent->_bf == -1){parent = parent->_parent;cur = cur->_parent;//沿着三岔链往上走}else if (parent->_bf == 0){break;}else if(parent->_bf == 2 || parent->_bf == -2)//开始旋转{//左单旋if (parent->_bf == 2 && cur->_bf == 1){RotaL(parent);}else if (parent->_bf == -2 && cur->_bf == -1){RotaR(parent);}else if (parent->_bf == -2 && cur->_bf == 1)//左右单旋{RotaLR(parent);}else if (parent->_bf == 2 && cur->_bf == -1){RotaRL(parent);}else{assert(false);//直接报错,平衡因子已经出问题}break;}else{assert(false);//直接报错,插入之前平衡因子已经出问题}}return true;}

测试二叉树是否为AVL树

中序遍历有序只能代表是搜索树;

通过高度和节点的平衡因子是否计算正确来判断是否为AVL树,如果不是AVL树,大量随机插入后某个节点左右子树高度差>=2,高度差即可和当前节点平衡因子比较。即可判断出是否为AVL树,同时注意需要递归,左右子树也需要保持AVL树。

        bool IsBalance()//判断AVL树{return _IsBalance(_root);}
private://需要私有中写子函数,因为需要递归bool _IsBalance(Node* root){if (root == nullptr)return true;int left = height(root->_left);int right = height(root->_right);int diff = right - left;if (abs(diff) >= 2)return false;// 如果计算出的平衡因子与pRoot的平衡因子不相等,或者// pRoot平衡因子的绝对值超过1,则一定不是AVL树if (diff != root->_bf || (diff > 1 || diff < -1))return false;return abs(diff) < 2 && _IsBalance(root->_left) && _IsBalance(root->_right);//判断AVL树,也要递归判断所有子树是否为AVL树}int height(Node* root){if (root == nullptr)return 0;int k1 = height(root->_left);int k2 = height(root->_right);return max(k1,k2)+1;}

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
一帆风顺二龙腾飞三阳开泰祝福语... 本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...