数据结构【AVL树模拟实现】
创始人
2024-01-28 17:52:28

目录

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;}

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
上海有几个区 最新上海16区划... 你平常会使用地图吗?一幅标准的地图可以帮助我们解决很多问题。自然资源部最新公布了长三角标准地图,上海...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
男生摸女生胸 同桌把我带到卧室... 所谓“酒色财气”,酒与色往往是连在一起的,澳洲有个医学生,就因为两次酒后猥亵自己的同学,几乎葬送了自...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...