因为上次普通的二叉搜索树在极端情况下极容易造成我们的链式结构(这会导致我们查询的时间复杂度变为O(n)),然而AVL树就很好的解决了这一问题(归功于四种旋转的方法),它让我们的树的查询的时间复杂度变得接近于甚至等于O(logN)。它相比于普通的二叉搜索树,它增加了平衡因子来维持树的高度,增加了纸箱上一个节点的parent指针。另外,平衡因子的计算方法是右子树的高度减去左子树的高度,当当前节点的平衡因子的值为-1、0、1的时候,我们认为当前节点是平衡的,当为其他值的时候就不平衡,需要通过旋转来将树调整平衡。
废话不多说,让我们了解一下四种旋转方式吧。
一、右旋
右旋的情况(最小)出现在一直向左子树插入数据,就像这样:
最后插入的6让10的平衡因子变为-2,让本就不怎么平衡的数变得彻底不平衡,因此就需要右旋。它的规则是什么呢?
当树满足右旋的条件的时候(当前节点的平衡因子为2或者-2),右旋的时候,平衡因子为-2,在这里满足条件的是10节点,我们把它的左子树的右子树给该节点的左子树,即:用节点10替换掉8的右子树,但是8的右子树是有东西的,而10的左子树刚好又不指向8,那么就进行互换一次。
最后再次修改平衡因子:
下边可以看一个实例:
这是一棵已经满足AVL树的树:
在5的左子树插入数据:
节点更新到6,不满足条件,需要旋转:
更新平衡因子:
用过重复的测试和观察可以发现,到最后,旋转的节点和他的左子树最后的平衡因子都是从-2、-1变到0、0.
值得注意的是在插入节点后,需要向上更新平衡因子,倘若更新后遇到平衡因子为0,那就没有再次向上更新的必要了。
插入代码结构如下:
//AVL树的插入bool Insert(K key){Node* cur = _root;Node* parent = nullptr;//插入数据if (cur == nullptr){_root = new Node(key);}else{//寻找插入的位置while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{return false;}}//插入cur = new Node(key);if (key > parent->_key)//插入的值大于父亲节点,那么就需要在父亲节点的右边插入{parent->_right = cur;parent->_right->_parent = parent;}else if(key < parent->_key)//插入的值小于父亲节点,那么就需要在父亲节点的左边插入{parent->_left = cur;parent->_left->_parent = parent;}else{assert(false);}}//更新平衡因子while (parent){if (cur == parent->_left){parent->_bl--;}else if (cur == parent->_right){parent->_bl++;}//检查树是否平衡if (parent->_bl == 0)//平衡,退出函数{return true;}else if (parent->_bl == 1 || parent->_bl == -1)//半平衡,向上更新,直到为0或者更新到根{cur = parent;parent = parent->_parent;}else if (parent->_bl == 2 || parent->_bl == -2)//不平衡,旋转{这里是四种旋转的区域return true;}else{assert(false);}}return true;}
那么知道原理后,就来用代码实现以下:
//右旋void RotateR(Node* node){Node* kidL = node->_left;Node* kidLR = node->_left->_right;Node* pparent = node->_parent;//先把该节点移动到kidl的右边node->_left = kidLR;if (kidLR)//只有在kidLR不为空的情况下才可以更新kidLR的父亲节点{kidLR->_parent = node;}kidL->_right = node;node->_parent = kidL;if (pparent == nullptr)//如果被旋转节点的父亲为空,则说明此节点为根节点{_root = kidL;kidL->_parent = nullptr;}else//不为根节点{if (node == pparent->_left)//判断被旋转的节点在父亲节点的左边还是右边{pparent->_left = kidL;}else{pparent->_right = kidL;}kidL->_parent = pparent;}kidL->_bl = node->_bl = 0;//更新平衡因子}
在写代码的过程中,为了防止出错,建议把需要更改的节点的位置用指针记录下来,一方面是为了防止在写代码的过程中因为误操作修改了指针的指向,导致再次使用的时候因为指针的变化从而操作本不应该操作的节点,另一方面是优化代码可读性,毕竟连续的“->”会让人感觉头疼。
看看示例:
int main()
{AVLTree<int> tree;tree.Insert(10);tree.Insert(8);tree.Insert(7);tree.Insert(6);tree.Insert(5);tree.Insert(4);tree.InTraversal();return 0;
}
运行结果(中序遍历):
二、左旋
左旋和右旋一样,它的发生情况是这种的:
它和右旋一样,不过是方向相反的,我们需要把13的左边给10的右边,然后把10给13的左边,做后需要注意的细节和右旋一样,就是父亲节点的更新。这一点需要大家画图研究一下。
另外,经过观察可以得到,左旋的条件是被旋转的节点平衡因子为2,它的右子树的平衡因子为1。
由于和右旋相反,所以可以在右旋代码中做修改(几乎所有的被改数值都和右旋相反):
//左旋void RotateL(Node* node){Node* kidR = node->_right;Node* kidRL = node->_right->_left;Node* pparent = node->_parent;//先把该节点移动到kidl的左边node->_right = kidRL;if (kidRL)//只有在kidRL不为空的情况下才可以更新kidRL的父亲节点{kidRL->_parent = node;}kidR->_left = node;node->_parent = kidR;if (pparent == nullptr)//如果被旋转节点的父亲为空,则说明此节点为根节点{_root = kidR;kidR->_parent = nullptr;}else//不为根节点{if (node == pparent->_left)//判断被旋转的节点在父亲节点的左边还是右边{pparent->_left = kidR;}else{pparent->_right = kidR;}kidR->_parent = pparent;}kidR->_bl = node->_bl = 0;//更新平衡因子}
示例:
int main()
{AVLTree<int> tree;tree.Insert(1);tree.Insert(2);tree.Insert(3);tree.Insert(4);tree.Insert(5);tree.Insert(6);tree.InTraversal();return 0;
}
运行结果:
三、左右双旋
左右双旋的情况(最小)大致是这样:
这种情况比较复杂,但是解决方法就是先对5进行左旋,再对10进行右旋,对5左旋是为了把这棵树变为纯粹的左边高(旋转后满足右旋的条件),然后再进行右旋。
在这里有一个值得注意的问题:被调整的节点的左子树的右子树的高度会影响被调整的节点的左子树或者被调整的节点的平衡因子,举个例子:
1.上边的例子是8的平衡因子为0的情况,那么最后parent(10)和kid(5)的平衡因子为0。
2.当8的平衡因子为1的时候(框里的代表能够满足左右双旋的情况)影响的是被调整的节点的左子树的平衡因子,即5的平衡因子
a.在8节点右边插入节点:
旋转:
最后5的节点的平衡因子为-1,10的节点的平衡因子为0。
b. a.在8节点左边插入节点:
最后5的节点的平衡因子为0,10的节点的平衡因子为1。
另外通过观察可得被旋转的节点的平衡因子为-2,它的左子树平衡因子为1的时候,发生左右旋转。
代码如下:
//左右双旋void RotateLR(Node* node){Node* kidL = node->_left;Node* kidLR = node->_left->_right;int bl = kidLR->_bl;//先对左边的节点进行左旋RotateL(kidL);//再对该节点进行右旋RotateR(node);if (bl == 0){node->_bl = kidL->_bl = kidLR->_bl = 0;}else if (bl == 1){kidL->_bl = -1;node->_bl = kidLR->_bl = 0;}else if (bl == -1){node->_bl = 1;kidL->_bl = kidLR->_bl = 0;}}
测试示例:
int main()
{AVLTree<int> tree;tree.Insert(10);tree.Insert(5);tree.Insert(11);tree.Insert(4);tree.Insert(8);tree.Insert(9);tree.InTraversal();return 0;
}
运行结果:
四、右左双旋
同左旋和右旋的关系,他们只是方向相反,能更改的东西也应该相反,它的形成条件如下图:
代码:
//右左双旋void RotateRL(Node* node){Node* kidR = node->_right;Node* kidRL = kidR->_left;int bl = kidRL->_bl;//先对右边的节点进行右旋RotateR(kidR);//再对该节点进行左旋RotateL(node);if (bl == 0){node->_bl = kidR->_bl = kidRL->_bl = 0;}else if (bl == 1){node->_bl = -1;kidR->_bl = kidRL->_bl = 0;}else if (bl == -1){kidR->_bl = 1;node->_bl = kidRL->_bl = 0;}else{return assert(false);}}
示例:
int main()
{AVLTree<int> tree;tree.Insert(10);tree.Insert(9);tree.Insert(15);tree.Insert(11);tree.Insert(16);tree.Insert(12);tree.Insert(13);tree.InTraversal();return 0;
}
总代码:
#include<iostream>
#include<assert.h>using namespace std;template <class K>
struct AVLTreenode
{AVLTreenode(K key):_key(key){}K _key;AVLTreenode* _left = nullptr;AVLTreenode* _right = nullptr;AVLTreenode* _parent = nullptr;int _bl = 0;
};template <class K>
class AVLTree
{
private:using Node = AVLTreenode<K>;Node* _root = nullptr;
public:void _InTraversal(Node* p){if (p == nullptr)return;_InTraversal(p->_left);cout << p->_key << " ";_InTraversal(p->_right);}//搜索树的中序遍历void InTraversal(){_InTraversal(_root);}//搜索树的查找Node* Find(const K& key){assert(_root);Node* cur = _root;while (cur){if (cur->_key > key){cur = cur->_left;}else if (cur->_key < key){cur = cur->_right;}else{return cur;}}return nullptr;}//AVL树的插入bool Insert(K key){Node* cur = _root;Node* parent = nullptr;//插入数据if (cur == nullptr){_root = new Node(key);}else{//寻找插入的位置while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{return false;}}//插入cur = new Node(key);if (key > parent->_key)//插入的值大于父亲节点,那么就需要在父亲节点的右边插入{parent->_right = cur;parent->_right->_parent = parent;}else if(key < parent->_key)//插入的值小于父亲节点,那么就需要在父亲节点的左边插入{parent->_left = cur;parent->_left->_parent = parent;}else{assert(false);}}//更新平衡因子while (parent){if (cur == parent->_left){parent->_bl--;}else if (cur == parent->_right){parent->_bl++;}//检查树是否平衡if (parent->_bl == 0)//平衡,退出函数{return true;}else if (parent->_bl == 1 || parent->_bl == -1)//半平衡,向上更新,直到为0或者更新到根{cur = parent;parent = parent->_parent;}else if (parent->_bl == 2 || parent->_bl == -2)//不平衡,旋转{if (parent->_bl == -2 && parent->_left->_bl == -1)//右旋{RotateR(parent);}else if (parent->_bl == 2 && parent->_right->_bl == 1)//左旋{RotateL(parent);}else if (parent->_bl == -2 && parent->_left->_bl == 1)//左右双旋{RotateLR(parent);}else if (parent->_bl == 2 && parent->_right->_bl == -1)//右左双旋{RotateRL(parent);}return true;}else{assert(false);}}return true;}//右旋void RotateR(Node* node){Node* kidL = node->_left;Node* kidLR = node->_left->_right;Node* pparent = node->_parent;//先把该节点移动到kidl的右边node->_left = kidLR;if (kidLR)//只有在kidLR不为空的情况下才可以更新kidLR的父亲节点{kidLR->_parent = node;}kidL->_right = node;node->_parent = kidL;if (pparent == nullptr)//如果被旋转节点的父亲为空,则说明此节点为根节点{_root = kidL;kidL->_parent = nullptr;}else//不为根节点{if (node == pparent->_left)//判断被旋转的节点在父亲节点的左边还是右边{pparent->_left = kidL;}else{pparent->_right = kidL;}kidL->_parent = pparent;}kidL->_bl = node->_bl = 0;//更新平衡因子}//左旋void RotateL(Node* node){Node* kidR = node->_right;Node* kidRL = node->_right->_left;Node* pparent = node->_parent;//先把该节点移动到kidl的左边node->_right = kidRL;if (kidRL)//只有在kidRL不为空的情况下才可以更新kidRL的父亲节点{kidRL->_parent = node;}kidR->_left = node;node->_parent = kidR;if (pparent == nullptr)//如果被旋转节点的父亲为空,则说明此节点为根节点{_root = kidR;kidR->_parent = nullptr;}else//不为根节点{if (node == pparent->_left)//判断被旋转的节点在父亲节点的左边还是右边{pparent->_left = kidR;}else{pparent->_right = kidR;}kidR->_parent = pparent;}kidR->_bl = node->_bl = 0;//更新平衡因子}//左右双旋void RotateLR(Node* node){Node* kidL = node->_left;Node* kidLR = node->_left->_right;int bl = kidLR->_bl;//先对左边的节点进行左旋RotateL(kidL);//再对该节点进行右旋RotateR(node);if (bl == 0){node->_bl = kidL->_bl = kidLR->_bl = 0;}else if (bl == 1){kidL->_bl = -1;node->_bl = kidLR->_bl = 0;}else if (bl == -1){node->_bl = 1;kidL->_bl = kidLR->_bl = 0;}else{return assert(false);}}//右左双旋void RotateRL(Node* node){Node* kidR = node->_right;Node* kidRL = kidR->_left;int bl = kidRL->_bl;//先对右边的节点进行右旋RotateR(kidR);//再对该节点进行左旋RotateL(node);if (bl == 0){node->_bl = kidR->_bl = kidRL->_bl = 0;}else if (bl == 1){node->_bl = -1;kidR->_bl = kidRL->_bl = 0;}else if (bl == -1){kidR->_bl = 1;node->_bl = kidRL->_bl = 0;}else{return assert(false);}}};