做网站py和php列表网推广效果怎么样
⭐上篇文章:34.二叉树进阶3(C++STL 关联式容器,set/map的介绍与使用)-CSDN博客
⭐本篇代码:c++学习/19.map和set的使用用与模拟 · 橘子真甜/c++-learning-of-yzc - 码云 - 开源中国 (gitee.com)
⭐标⭐是比较重要的部分
一. 二叉搜索树的缺点
之前文章中提到,普通的二叉搜索树在某些情况下会退出成链表,或者根节点的左右子树的高度差非常大。这个时候就会导致其搜索效率由 O(logN) -> O(N)。
为了解决这个问题,计算机科学家提供了AVL树。AVL树一种平衡二叉搜索树,通过平衡因子和旋转操作来保证二叉搜索树是平衡的。
二. AVL树及其特点
1 AVL树是一颗二叉搜索树,满足二叉搜索树的性质
2 AVL树每一个节点中有一个平衡因子,表示该节点左右子树的高度差的绝对值,且该值不能超过1(即左右子树高度差不能超过1)
可以看到,通过平衡因子可以保证AVL树是高度平衡的!AVL树在每一次插入新节点之后都要检验该节点和其父节点的平衡因子,一旦检测到平衡因子的值超过1,就要通过旋转操作来调整平衡因子。
三. AVL树的节点和旋转操作图解
3.1 AVL树节点
//节点
template<class K, class V>
struct AVLTreeNode
{AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;//节点的双亲,用于更新平衡因子int _bf;//平衡因子 balance factor pair<K, V> _kv;//构造函数AVLTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _bf(0), _kv(kv){}
};
二叉树一般都是存储键值对<k,v>。AVL树需要一个平衡因子bf
3.2 AVL树结构
template<class K, class V>
class AVlLTree
{typedef AVLTreeNode<K, V> Node;
public:bool Insert(const pair<K, V>& kv){}
private:Node* _root;
};
3.3 AVL树的插入操作 ⭐
AVL树的插入操作和二叉搜索树几乎一样,不过在插入节点之后要根据平衡因子才调整整棵树的结构。
a 左单旋
如果一个节点的平衡因子为2,即右子树比左子树高2则需要对该节点进行左单旋
代码如下:
//右右,左单旋.一共需要调整6条线,四个节点void RotateLeft(Node* parent){if (!parent)return;Node* ppNode = parent->_parent;//要旋转节点的父亲Node* SubR = parent->_right;//要旋转节点的右孩子Node* SubRLeft = SubR->_left;//要旋转节点右孩子的左孩子//一:调整节点parent->_right = SubRLeft;if (SubRLeft)SubRLeft->_parent = parent;SubR->_left = parent;parent->_parent = SubR;//1.parent是根,现在SubR是根//2.parent是整棵树的子树,找到其父亲,旋转完成后,让subR与其父亲相连接if (_root == parent){_root = SubR;SubR->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = SubR;}else{ppNode->_right = SubR;}SubR->_parent = ppNode;}//二:更新平衡因子parent->_bf = SubR->_bf = 0;}
b 右单旋
如果一个节点的左子树比右子树高2,则需要对该节点进行右单旋
代码如下:
//左左,右单旋void RotateRight(Node* parent){if (!parent)return;Node* ppNode = parent->_parent;Node* SubL = parent->_left;Node* SubLRight = SubL->_right;//1.旋转,调整节点parent->_left = SubLRight;if (SubLRight)SubLRight->_parent = parent;SubL->_right = parent;parent->_parent = SubL;if (_root == parent){_root = SubL;SubL->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = SubL;}else{ppNode->_right = SubL;}SubL->_parent = ppNode;}//2.更新平衡因子//旋转之后,subL为子树根,左子树高度为h+1,右子树为h+parent高度1.左右子树高度一样,平衡因子为0//parent左右子树高度也相等,平衡因子为0parent->_bf = SubL->_bf = 0;}
c 左双旋
如果新增节点在较高右子树的左侧,则需要两次旋转。如下图
双旋可以复用单旋的代码
//右左void RotateRightLeft(Node* parent){//注意控制三个节点的平衡因子Node* SubR = parent->_right;Node* SubRL = SubR->_left;int bf = SubRL->_bf;RotateRight(parent->_right); //上图旋转10RotateLeft(parent); //上图旋转5//对应图来理解平衡因子if (bf == -1)//右左节点的左边插入{parent->_bf = 0;SubR->_bf = 1;}else if (bf == 1)//右左节点的右边插入{SubR->_bf = 0;parent->_bf = -1;}else if (bf == 0)//此时SubRL就是新增节点{parent->_bf = 0;SubR->_bf = 0;}//此时右左节点是根节点SubRL->_bf = 0;}
c 右双旋
如果新增节点在较高左子树的右侧,则需要两次旋转。如下图
//左右,先左旋parent->left,再右旋parentvoid RotateLeftRight(Node* parent){Node* SubL = parent->_left;Node* SubLR = SubL->_right;int bf = SubLR->_bf;RotateLeft(SubL); //上图旋转5RotateRight(parent); //上图旋转10//更新平衡因子if (bf == 1){parent->_bf = 0;SubL->_bf = -1;}else if (bf == -1){SubL->_bf = 0;parent->_bf = 1;}else if (bf == 0){SubL->_bf = 0;parent->_bf = 0;}//左右节点为根节点SubLR->_bf = 0;}
注意,每一次旋转之后都要更新平衡因子
四. 二叉树插入完整代码
#pragma once
#include<iostream>
#include<queue>
#include<string>
using namespace std;//AVL树 (高度平衡二叉搜索树)
//1.是二叉搜索树
//2.树及所有子树都要满足左右左右子树的高度差不超过1//为了方便实现,我们在这里引入了平衡因子的概念,其值范围只能是0,1,-1(平衡因子不是必须的)
//平衡因子=右子树的高度-左子树的高度
//这样就能够控制其高度位O(logN)//节点
template<class K, class V>
struct AVLTreeNode
{AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;//节点的双亲,用于更新平衡因子int _bf;//平衡因子 balance factor pair<K, V> _kv;//构造函数AVLTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _bf(0), _kv(kv){}
};//AVLTree
//1.按照搜索树的规则插入
//2.更新平衡因子
//3.如果没有出现违规的平衡因子,插入结束
//4.如果有存在违规的平衡因子,需要旋转
template<class K, class V>
class AVlLTree
{typedef AVLTreeNode<K, V> Node;
public://一个节点的插入,只会影响其祖先的平衡因子,所以只要判断其祖先的平衡因子即可//1.如果cur是其父亲的左,parent->_bf--.如果cur是其父亲的右,parent->_bf++//2.在这些祖先中,更新完,parent->_bf==0,说明parent的高度不变,更新结束,插入完成(把这颗parent为根的子树矮的填上了)//3.更新完parent,parent->_bf==1 or -1,parent高度变了,继续往上更新(说明更新前parent->_bf==0,parent的高度变了)\//4.如果更新完parent的bf,parent->_bf==2, or -2,parent的所在子树不平衡,需要旋转处理,旋转后插入结束bool Insert(const pair<K, V>& kv){//1.先按搜索树进行插入if (_root == nullptr){_root = new Node(kv);return true;}Node* parent = nullptr;Node* cur = _root;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;cur->_parent = parent;//要将cur与其父亲链接}else{parent->_right = cur;cur->_parent = parent;}//2.从插入节点开始对这个节点及所有祖先节点更新平衡因子while (parent){if (parent->_right == cur){parent->_bf++;}else{parent->_bf--;}if (parent->_bf == 0){//parent所在子树高度不变,更新结束break;}else if (parent->_bf == 1 || parent->_bf == -1){//parent所在子树高度变了,有0变1或者-1,此时继续向上更新cur = parent;parent = parent->_parent;}else //if (parent->_bf == 2 || parent->_bf == -2){//parent所在子树出现问题,需要旋转处理//1.旋转完成后,它还得是完整的搜索树//2.旋转完成后,它要变平衡//旋转方法if (parent->_bf == 2)//左旋{if (cur->_bf == 1)//右右,直接对parent左单旋{//1.左单旋//将subR的左边给parent的右边(parent的右边指向subR的左边)//将parent变为subR的左边(subR的左边指向parent)RotateLeft(parent);}else if (cur->_bf == -1)//右左,先对parent->right右单旋,再对parent左单旋{//左双旋//先右单旋cur,让parent变为右右//再左单旋parentRotateRightLeft(parent);} }else if (parent->_bf == -2){ if (cur->_bf == -1)//左左,直接对parent右单旋{//右单旋RotateRight(parent);}else if (cur->_bf == 1)//左右,对parent->left左单旋,再对parent右单旋{//右双旋//先左单旋cur,让parenttt变为左左//再右单旋parent//此时parent左右这个节点为根RotateLeftRight(parent);}}//旋转完成后之间跳出即可,这是由于旋转让我平衡了,高度恢复到了插入新节点之前的高度(即高度不会变化)//如果是子树,对上层节点不会有影响。更新结束,跳出即可break; }}return true;}void InOrder(){_InOrder(_root);cout << endl;}
private://右右,左单旋.一共需要调整6条线,四个节点void RotateLeft(Node* parent){if (!parent)return;Node* ppNode = parent->_parent;//要旋转节点的父亲Node* SubR = parent->_right;//要旋转节点的右孩子Node* SubRLeft = SubR->_left;//要旋转节点右孩子的左孩子//一:调整节点parent->_right = SubRLeft;if (SubRLeft)SubRLeft->_parent = parent;SubR->_left = parent;parent->_parent = SubR;//1.parent是根,现在SubR是根//2.parent是整棵树的子树,找到其父亲,旋转完成后,让subR与其父亲相连接if (_root == parent){_root = SubR;SubR->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = SubR;}else{ppNode->_right = SubR;}SubR->_parent = ppNode;}//二:更新平衡因子parent->_bf = SubR->_bf = 0;}//左左,右单旋void RotateRight(Node* parent){if (!parent)return;Node* ppNode = parent->_parent;Node* SubL = parent->_left;Node* SubLRight = SubL->_right;//1.旋转,调整节点parent->_left = SubLRight;if (SubLRight)SubLRight->_parent = parent;SubL->_right = parent;parent->_parent = SubL;if (_root == parent){_root = SubL;SubL->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = SubL;}else{ppNode->_right = SubL;}SubL->_parent = ppNode;}//2.更新平衡因子//旋转之后,subL为子树根,左子树高度为h+1,右子树为h+parent高度1.左右子树高度一样,平衡因子为0//parent左右子树高度也相等,平衡因子为0parent->_bf = SubL->_bf = 0;}//右左void RotateRightLeft(Node* parent){//注意控制三个节点的平衡因子Node* SubR = parent->_right;Node* SubRL = SubR->_left;int bf = SubRL->_bf;RotateRight(parent->_right);RotateLeft(parent);//对应图来理解平衡因子if (bf == -1)//右左节点的左边插入{parent->_bf = 0;SubR->_bf = 1;}else if (bf == 1)//右左节点的右边插入{SubR->_bf = 0;parent->_bf = -1;}else if (bf == 0)//此时SubRL就是新增节点{parent->_bf = 0;SubR->_bf = 0;}//此时右左节点是根节点SubRL->_bf = 0;}//左右,先左旋parent->left,再右旋parentvoid RotateLeftRight(Node* parent){Node* SubL = parent->_left;Node* SubLR = SubL->_right;int bf = SubLR->_bf;RotateLeft(SubL);RotateRight(parent);if (bf == 1){parent->_bf = 0;SubL->_bf = -1;}else if (bf == -1){SubL->_bf = 0;parent->_bf = 1;}else if (bf == 0){SubL->_bf = 0;parent->_bf = 0;}//左右节点为根节点SubLR->_bf = 0;}//中序遍历辅助函数void _InOrder(Node* root){if (!root)return;_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << " 平衡因子:" << root->_bf << endl;_InOrder(root->_right);}Node* _root;
};