软件技术属于什么学类网站优化招商
目录
1.AVL树的概念
2.AVL树的实现
2.1 AVL树节点的定义
2.2 AVL树的插入结构
2.3 AVL树的旋转
2.3.1 左单旋
2.3.2 右单旋
2.3.3 右左双旋
2.3.4 左右双旋
2.4 总体的插入逻辑
1.AVL树的概念
二叉搜索树虽可以增加查找的效率,但如果数据有序或接近有序,二叉搜索树将会退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因为两位俄罗斯的数学家发明了一种解决上述问题的方法:当向二叉搜索树中插入新节点后,如果能保证每个节点的左右子树高度之差的绝对值不超过1,即可降低树的高度,从而减少平均搜索长度。
一颗AVL树或者是空树,是具有以下性质的二叉搜索树:
- 它的左右子树都是AVL树"高度平衡二叉树"
- 左右子树高度之差(平衡因子)的绝对值不超过1(-1、0、1)
|右子树高度-左子树高度|≤1,平衡为什么不是高度相等呢?有时候是真的做不到。
2.AVL树的实现
2.1 AVL树节点的定义
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;explicit AVLTreeNode(const pair<K, V>& kv): _kv(kv), left(nullptr), right(nullptr), parent(nullptr), _bf(0){}
};
2.2 AVL树的插入结构
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(kv.first < cur->_kv.first){parent = cur;cur = cur->left;}else if(kv.first > cur->_kv.first){parent = cur;cur = cur->right;}elsereturn false;}cur = new Node(kv);if(kv.first < parent->_kv.first)parent->left = cur;elseparent->right = cur;cur->_parent = parent;//控制平衡,更新平衡因子_bf}
private:Node* _root = nullptr;
};
上面是插入了结点,下面就该考虑如何平衡了:需要分情况讨论:
插入结点就会改变 cur 的 _parent 的 _bf:
新增在左:parent 的因子 --
20(-1)/
10(0)
新增在右:parent 的因子 ++
20(1)\30(0)
更新后 parent 的因子 == 0,parent的子树高度不变,不会影响祖先,不再向上更新:
20(0)/ \
10 30
更新后 parent 因子 == 1 or -1,说明parent的平衡因子原来是0,现在高度变了,需要沿parent逐级向上更新,因为当前树变的不平衡,一定会影响更高层的_bf,也就是根据parent的变化,更新parent->_parent的 _bf
20 (0)/ \
10 30(1) parent\40 cur
向上更新时,相当于cur新增在下面这个parent的右边,所以 parent 的 _bf --,由0变-1
20 parent (0)->(-1)/ \
10 30(1) cur\40
更新后 parent 因子 == 2 or -2,说明 parent 原来就不平衡,此时更不平衡了,需要对parent所在子树进行旋转
20 (2)/ \
10 30 (2) parent\40 (1) cur\50
更新到 cur 为根结点,也就是 parent 为 nullptr 时结束,说明更新到了最祖宗的结点
//控制平衡,更新平衡因子_bfwhile(parent){if(cur == parent->left)parent->_bf--;elseparent->_bf++;//检查更新后的_bfif(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){//旋转}else//万一不小心更新出了3,4,5...说明2的时候就出错了assert(false);}
2.3 AVL树的旋转
插入函数的结构我们很清楚了,下面来分析一下旋转的具体过程:
旋转的时候要注意两个问题:
- 保持树是搜索树
- 变成平衡树的同时降低树的高度
2.3.1 左单旋
新结点插入较高右子树的右侧-右右:左单旋
20 / \
10 30 (2) parent\40 (1) cur\50
核心操作:
parent->right = cur->left;
cur->left = parent;
30 (2) parent\40 (1) cur\50parent->right = cur->left;(parent)30 40 cur\ / \null 50cur->left = parent;40 cur/ \(parent)30 50\null
但是这时是有问题的,每个结点有三个方向,left, right, parent。
我们断开的是parent→right; cur→left, 但是只有这两步是不行的,他们之间复杂的指向关系还需要更新。
我们可以画个图:
所以我们的两个核心操作改变了三个结点之间的指向,cur、parent、cur→left
void rotateL(Node* parent){Node* cur = parent->_right;Node* curleft = cur->_left;parent->_right = curleft;if(curleft)curleft->_parent = parent;cur->_left = parent;Node* ppnode = parent->_parent;parent->_parent = cur;if(parent == _root)//如果旋转放下去的是根结点,那么cur就是新根,它的父结点就是nullptr{_root = cur;cur->_parent = nullptr;}else//如果parent之上还有结点,那么需要在前面修改parent的_parent之前保存ppnode,现在cur->_parent=ppnode{cur->_parent = ppnode;//然后我们还不知道ppnode的left还是right链接的parent,所以也不知道是left还是right链接curif(ppnode->_left == parent)ppnode->_left = cur;elseppnode->_right = cur;}parent->_bf = cur->_bf = 0;}
- parent为根结点,放到cur的left了,cur成为新的根节点,cur→_parent = nullptr
- parent之上还有父节点ppnode,ppnode需要连接子树新的根节点cur,就需要判断ppnode的左还是右去连接cur。
- 最后更新parent 和 cur 的_bf
2.3.2 右单旋
新结点添加在较高左子树的左侧-左左:右单旋
和左单旋类似,基本就是翻转过来,核心操作为:
parent->_left = cur->_right;
cur->_right = parent;
void rotateR(Node* parent)
{Node* cur = parent->_left;Node* curright = cur->_right;parent->_left = curright;if(curright)curright->_parent = parent;cur->_right = parent;Node* ppnode = parent->_parent;parent->_parent = cur;if(_root == parent){_root = cur;cur->_parent = nullptr;}else{cur->_parent = ppnode;if(ppnode->_left == parent)ppnode->_left = cur;elseppnode->_right = cur;}parent->_bf = cur->_bf = 0;
}
2.3.3 右左双旋
新增结点在较高右子树的左边:右左
20 (1) / \
10 30 / \25 40 20 (2) / \
10 30 (-1)/ \25 40 /24
若像原来,右子树高,parent 的 _bf = 2,走左单旋:
20 (2) parent/ \
10 30 (-1) cur/ \25 40 /2420 30 cur/ \ / \10 25 40 /2430/ \20 40/ \ 10 25 /24
还是不平衡,所以要先对cur子树进行右单旋,再对parent进行左单旋,即为双旋:
我们看黄线就可以明显的观察出双旋的本质:
第一个旋转是将 折线 变 直线,第二个旋转是将 直线 变 平衡。
void RotateRL(Node* parent)
{RotateR(parent->right);RotateL(parent);
}
但是在双旋中,因为我们调用的单旋最后都是平衡的,所以cur,curleft,parent 的 _bf 最后都修改为了0,在上图中我们可以看出这是不对的。
我们需要分情况讨论_bf的情况。
上例中我们在 60 的左边插入了新结点 40 导致的双旋,所以 60 的_bf 为-1,这种情况下,双旋完成后
cur->_bf = 1;
curleft->_bf = 0;
parent->_bf = 0;
如果在 60 的右插入新结点 50 导致双旋,那么 60 的_bf 为 1,这种情况下:
cur->_bf = 0;
curleft->_bf = 0;
parent->_bf = -1;
最后就是 60自己 引发的双旋,60 的_bf 是0,这种情况比较简单,旋转之后三个结点的平衡因子都是0
30 30\ \90 → 60 → 60/ \ / \60 90 30 90
void rotateRL(Node* parent){Node* cur = parent->_right;Node* curleft = cur->_left;int bf = curleft->_bf;rotateR(parent->_right);rotateL(parent);if(bf == 1){curleft->_bf = 0;parent->_bf = -1;cur->_bf = 0;}else if(bf == -1){curleft->_bf = 0;parent->_bf = 0;cur->_bf = 1;}else{curleft->_bf = parent->_bf = cur->_bf = 0;}}
2.3.4 左右双旋
新增结点在较高左子树的右边,与有左旋是大致相同的,基本镜像
void rotateLR(Node* parent){Node* cur = parent->_left;Node* curright = cur->_right;int bf = curright->_bf;rotateL(parent->_left);rotateR(parent);if(bf == 1){curright->_bf = 0;parent->_bf = 0;cur->_bf = -1;}else if(bf == -1){curright->_bf = 0;parent->_bf = 1;cur->_bf = 0;}else{curright->_bf = parent->_bf = cur->_bf = 0;}}
2.4 总体的插入逻辑
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(kv.first < cur->_kv.first){parent = cur;cur = cur->left;}else if(kv.first > cur->_kv.first){parent = cur;cur = cur->right;}elsereturn false;}cur = new Node(kv);if(kv.first < parent->_kv.first)parent->left = cur;elseparent->right = cur;cur->_parent = parent;//控制平衡,更新平衡因子_bfwhile(parent){if(cur == parent->left)--parent->_bf;else++parent->_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){if(cur->_bf == 1)rotateL(parent);elserotateRL(parent);}else{if(cur->_bf == -1)rotateR(parent);elserotateLR(parent);}break;}elseassert(false);}}