目录
前言:二叉树是数据结构的重要一部分,而作为平衡二叉搜索树的AVLTree和红黑树学习起来是比较难的,但是我们需要去了解它们的思路(在学习的过程中,我们最好通过画图去更深入的理解,然后根据自己的图来完成代码实现)
在学习AVLTree之前建议先去学习一下set和map(了解KV模型,键值对等)
set+map-》C++ 关联式容器map+set_糖果雨滴a的博客-CSDN博客
红黑树-》深入理解 红黑树【满足红黑树5条规则】_糖果雨滴a的博客-CSDN博客
实现一个含义3叉链的二叉树,并且包括pair类型的kv,以及为了方便控制平衡而设计出的平衡因子。
- template<class K, class V>
- struct AVLTreeNode
- {
- pair
_kv; - AVLTreeNode
* _left; - AVLTreeNode
* _right; - AVLTreeNode
* _parent; -
- // 右子树-左子树的高度差
- int _bf;
-
- AVLTreeNode(const pair
& kv) - : _kv(kv)
- , _left(nullptr)
- , _right(nullptr)
- , _parent(nullptr)
- , _bf(0)
- {}
-
- // AVL树没有规定必须要设计平衡因子
- // 平衡因子只是一个实现的选择,方便控制平衡
- };
为了后面方便,typedef为Node,然后创建一个Node类型的根结点。
- template<class K, class V>
- class AVLTree
- {
- typedef AVLTreeNode
Node; - private:
- Node* _root = nullptr;
- bool Insert(const pair
& kv) - {
- // 1.搜索树的规则插入
- // 2.看是否违反平衡规则,如果违反就需要处理:旋转
- if (_root == nullptr)
- {
- _root = new Node(kv);
- _root->_bf = 0;
- return true;
- }
-
- Node* parent = nullptr;
- Node* cur = _root;
- // 找到要插入的位置
- while (cur)
- {
- if (cur->_kv.first < kv.first)
- {
- parent = cur;
- cur = cur->_right;
- }
- else if (cur->_kv.first > kv.first)
- {
- parent = cur;
- cur = cur->_left;
- }
- else
- {
- return false;
- }
- }
-
- // 插入该元素
- cur = new Node(kv);
- if (parent->_kv.first < kv.first)
- {
- parent->_right = cur;
- }
- else
- {
- parent->_left = cur;
- }
-
- cur->_parent = parent;
-
- // 更新平衡因子
- // 最远要更新到根为止
- while (parent)
- {
- if (cur == parent->_right)
- {
- parent->_bf++;
- }
- else
- {
- parent->_bf--;
- }
-
- // 判断是否要继续更新
- // 1 or -1 -》 0 插入的节点填上了矮的那边
- if (parent->_bf == 0)
- {
- // 高度不变,更新结束
- break;
- }
- // 0 -》 1 or -1 插入节点导致一边变高了
- else if (parent->_bf == 1 || parent->_bf == -1)
- {
- // 子树的高度变了,继续更新祖先
- cur = cur->_parent;
- parent = parent->_parent;
- }
- // 1 or -1 -》 2 or -2 插入节点导致本来高的一边又变高了
- else if (parent->_bf == 2 || parent->_bf == -2)
- {
- // 子树不平衡 --需要旋转处理
- // 左单旋
- if (parent->_bf == 2 && cur->_bf == 1)
- {
- RotateL(parent);
- }
- // 右单旋
- else if (parent->_bf == -2 && cur->_bf == -1)
- {
- RotateR(parent);
- }
- // 左右双旋
- else if (parent->_bf == -2 && cur->_bf == 1)
- {
- RotateLR(parent);
- }
- // 右左双旋
- else if (parent->_bf == 2 && cur->_bf == -1)
- {
- RotateRL(parent);
- }
-
- break;
- }
- else
- {
- // 插入之前AVL就存在不平衡子树(平衡因子的绝对值>= 2的节点)
- assert(false);
- }
- }
-
- return true;
- }
首先我们注意一下该树是否为空,为空就创建一个节点,把该点平衡因子变为0即可。
然后因为是3叉链,所以我们要设置一个parent变量和一个cur变量。然后按照二叉搜索树的方法去找到要插入的位置(要插入的元素的first比cur大就往右走,比cur小就往左走),找到之后,new出该节点,然后插入,同时把该链接的都链接上。到这为止都很简单。
接下来就是AVLTree的重点了:更新平衡因子。
这里我们首先想想,如果插入了一个元素,那么该元素的parent的平衡因子一定会发生变化,那么parent的parent是否会变化呢?
这个就不一定了,可能会变化也可能不会变化,需要我们去分情况讨论。而不是该插入元素的祖先节点一定不会发生变化,因此最多会变化到根节点,但不会影响另一边的子树。
首先先更新该插入元素的parent的平衡因子,如果插入在右边,就++,如果插入在左边,就--,(这里我们设置右边节点多为正数+,左边节点多为负数-)。
更新了parent节点后,就有继续判断是否要继续往上更新了。
第一种情况:parent的平衡因子变成了0,那么说明插入的节点填上了矮的那边,高度没有发生变化,不会影响到上面的节点,因此不需要进行更新了,更新结束。
第二种情况:parent的平衡因子变成了1或者-1,这时说明的插入的节点导致一边变高了,这时候高度变了,那么就需要继续更新祖先的平衡因子,因此我们让cur变成parent,parent变成parent的parent,这样就往上走了一个节点,然后因为整个平衡因子更新在一个while循环中,所以循环到下一个再次进行更新平衡因子。
第三种情况:这种情况是AVLTree中最重要的地方,也是最难的地方。parent的平衡因子变成了2或者-2,这时说明插入的节点导致本来就高的一边又变高了,因此我们需要平衡该树(这里就需要进行旋转【旋转原则:①保持搜索树的规则 ②让子树变平衡】处理了)。这里我们一定要画图去理解。
如果出现第四种情况,说明插入之前AVL树就不平衡,即实现的有错误,断言报错。
如果只根据语言描述,几乎不能去理解AVLTree的旋转,因此下面我们通过画图的方式来理解AVLTree的旋转操作。
根据树高度的不同,树的样子会有所不同。这个就简单看一下。
这里因为树的高度不同,就可能会有很多种的画法,因此我们可以通过用下面这种可以代表所有情况的抽象图来进行旋转。
这里为什么我们要在parent的上面画一个节点呢?
因为我们不清楚parent是根还是整个子树的一个局部子树,因此我们要注意进行分情况讨论。
接下来说一下左单旋的使用场景和操作:
使用场景: 该cur节点的parent节点的平衡因子为2,cur节点的平衡因子为1时,这里图的样子大概就是一个向右的斜线。
操作:我们将cur节点parent节点定义为parent,将该parent的右孩子节点定义为subR,将subR的左孩子节点定义为subRL。
旋转方式:将subRL变为parent的右孩子节点,然后再左旋parent节点,让parent变为subR的左孩子节点。最后更新平衡因子。
通过语言的描述,再结合上面图中的操作,我们就可以很好的理解左单旋了。
接下来在通过代码实现来进一步理解左单旋:
- void RotateL(Node* parent)
- {
- Node* subR = parent->_right;
- Node* subRL = subR->_left;
-
- Node* ppNode = parent->_parent;
-
- parent->_right = subRL;
- parent->_parent = subR;
- if (subRL)
- {
- subRL->_parent = parent;
- }
- subR->_left = parent;
-
- if (parent == _root)
- {
- _root = subR;
- _root->_parent = nullptr;
- }
- else
- {
- if (ppNode->_left == parent)
- {
- ppNode->_left = subR;
- }
- else
- {
- ppNode->_right = subR;
- }
-
- subR->_parent = ppNode;
- }
-
- // 更新平衡因子
- subR->_bf = 0;
- parent->_bf = 0;
- }
首先定义3个变量,一个是subR,一个是subRL,还有一个是ppNode,这个ppNode就是为了在旋转后,可以让旋转后的subR能连上上面的子树。
然后把链接关系更改为旋转后的链接关系(这里要注意subRL可能是空)。
再分情况讨论,第一种是parent就是根节点,这种情况直接让subR变成新的根节点,然后在把subR的parent置为空。第二种是parent不是根节点,这时就要看parent是ppNode的左还是右,是左就让ppNode的左变为subR,是右就让ppNode的右变为subR。
最后根据画的图更新一下平衡因子即可。
右单旋与左单旋较为相似,我们看图来理解。
右单旋的使用场景和操作:
使用场景: 该cur节点的parent节点的平衡因子为-2,cur节点的平衡因子为-1时,这里图的样子大概就是一个向左的斜线。
操作:我们将cur节点parent节点定义为parent,将该parent的左孩子节点定义为subL,将subL的右孩子节点定义为subLR。
旋转方式:将subRL变为parent的左孩子节点,然后再右旋parent节点,让parent变为subL的右孩子节点。最后更新平衡因子。
代码实现:
- void RotateR(Node* parent)
- {
- Node* subL = parent->_left;
- Node* subLR = subL->_right;
-
- Node* ppNode = parent->_parent;
-
- parent->_left = subLR;
- parent->_parent = subL;
-
- if (subLR)
- {
- subLR->_parent = parent;
- }
- subL->_right = parent;
-
- if (parent == _root)
- {
- _root = subL;
- _root->_parent = nullptr;
- }
- else
- {
- if (parent == ppNode->_left)
- {
- ppNode->_left = subL;
- }
- else
- {
- ppNode->_right = subL;
- }
-
- subL->_parent = ppNode;
- }
-
- subL->_bf = 0;
- parent->_bf = 0;
- }
实现方式也很类似左单旋,先定义变量,然后更改链接关系,接着分情况讨论parent是否为根节点,最后更新平衡因子。
先简单看一下不同高度的发生左右双旋的样子。
通过抽象图来实现左右双旋:
根据上面的图,我们可以发现根据插入节点的位置不同,旋转出来的不同节点的平衡因子是不同的,因此双旋是需要分情况讨论的,当然这几种情况相同的都是先左旋parent->_left节点,再右旋parent节点,然后就要分情况去更新平衡因子了。(这里我们仔细观察,会发现subLR在3种情况都不同,因此我们可以根据subLR进行分情况)
第一种情况(subLR为0):这种情况是发生左右双旋的最低高度,旋转后parent、subL、subLR的平衡因子都为0。
第二种情况(subLR为-1):旋转后parent = 1,subL = 0,subLR = 0
第三种情况(subLR为1):旋转后parent = 0,subL = -1,subLR = 0
左右单旋的使用场景和操作:
使用场景: 该cur节点的parent节点的平衡因子为-2,cur节点的平衡因子为1时,这里图的样子大概就是一个先向左后向右的折线。
操作:我们将cur节点parent节点定义为parent,将该parent的左孩子节点定义为subL,将subL的右孩子节点定义为subLR。
旋转方式:先对parent->_left(subL)进行左旋,将subLR的左孩子节点变为subL的右孩子节点,再左旋subL节点,让subL变为subLR的左孩子节点。然后再对parent进行右旋,将subLR的右孩子节点变为parent的左孩子节点,再右旋parent节点,让parent变为subLR的右孩子节点。最后更新平衡因子
代码实现:
- void RotateLR(Node* parent)
- {
- Node* subL = parent->_left;
- Node* subLR = subL->_right;
- int bf = subLR->_bf;
-
- RotateL(parent->_left);
- RotateR(parent);
-
- // 更新平衡因子(分三种情况)
- if (bf == 0)
- {
- parent->_bf = 0;
- subL->_bf = 0;
- subLR->_bf = 0;
- }
- else if (bf == 1)
- {
- parent->_bf = 0;
- subL->_bf = -1;
- subLR->_bf = 0;
- }
- else if (bf == -1)
- {
- parent->_bf = 1;
- subL->_bf = 0;
- subLR->_bf = 0;
- }
- else
- {
- // subLR->_bf旋转前就有问题
- assert(false);
- }
- }
旋转操作就直接调用实现过的左单旋和右单旋函数即可。
这里定义的变量依旧是subL、subLR,这里不需要定义ppNode,但是需要定义一个bf(因为我们需要根据subLR的平衡因子来分情况讨论)。
最后就是根据图来分情况更新平衡因子,如果出现这些情况之外,说明在旋转前就有问题(防止自己前面实现错误,而不知道在哪出错),就断言报错。
与左右双旋类似,直接看图:
右左双旋同样是需要分情况讨论的(这里依旧是根据subRL进行分情况)
第一种情况(subRL为0):这种情况是发生左右双旋的最低高度,旋转后parent、subR、subRL的平衡因子都为0。
第二种情况(subRL为-1):旋转后parent = 0,subR = 1,subRL = 0
第三种情况(subRL为1):旋转后parent = -1,subR = 0,subRL = 0
左右单旋的使用场景和操作:
使用场景: 该cur节点的parent节点的平衡因子为2,cur节点的平衡因子为-1时,这里图的样子大概就是一个先向右后向左的折线。
操作:我们将cur节点parent节点定义为parent,将该parent的右孩子节点定义为subR,将subR的左孩子节点定义为subRL。
旋转方式:先对parent->_right(subR)进行右旋,将subRL的右孩子节点变为subR的左孩子节点,再右旋subR节点,让subR变为subRL的右孩子节点。然后再对parent进行左旋,将subRL的左孩子节点变为parent的右孩子节点,再左旋parent节点,让parent变为subRL的左孩子节点。最后更新平衡因子
代码实现:
- void RotateRL(Node* parent)
- {
- Node* subR = parent->_right;
- Node* subRL = subR->_left;
- int bf = subRL->_bf;
-
- RotateR(parent->_right);
- RotateL(parent);
-
- // 更新平衡因子(分三种情况)
- if (bf == 0)
- {
- parent->_bf = 0;
- subR->_bf = 0;
- subRL->_bf = 0;
- }
- else if (bf == 1)
- {
- parent->_bf = -1;
- subR->_bf = 0;
- subRL->_bf = 0;
- }
- else if (bf == -1)
- {
- parent->_bf = 0;
- subR->_bf = 1;
- subRL->_bf = 0;
- }
- else
- {
- // subRL->_bf旋转前就有问题
- assert(false);
- }
- }
旋转操作就直接调用实现过的右单旋和左单旋函数即可。
这里依旧是定义变量,然后再根据画的图来更新平衡因子。
最后,我们再实现一个判断函数,因为我们并不知道我们实现的到底是不是平衡二叉搜索树。
注意空树也属于平衡二叉搜索树,然后我们通过得到左右子树的平衡因子(高度差),再对这个平衡因子的大小进行判断,如果出现平衡因子的绝对值>=2的情况就说明该节点的平衡因子异常,如果出现该计算得到的平衡因子与上面得到的平衡因子大小不同,则说明该节点平衡因子不符合实际,这两种情况都说明不是平衡二叉搜索树。
判断完该节点后,需要继续判断其它节点,这里我们采用递归的方法依次向左右子树内部去进行判断。
- bool _IsBalanceTree(Node* root)
- {
- // 空树也是AVL树
- if (nullptr == root)
- return true;
-
- // 计算pRoot节点的平衡因子:即pRoot左右子树的高度差
- int leftHeight = _Height(root->_left);
- int rightHeight = _Height(root->_right);
- int diff = rightHeight - leftHeight;
-
- // 如果计算出的平衡因子与pRoot的平衡因子不相等,
- // 或者pRoot平衡因子的绝对值超过1,则一定不是AVL树
- if (abs(diff) >= 2)
- {
- cout << root->_kv.first << "节点平衡因子异常" << endl;
- return false;
- }
-
- if (diff != root->_bf)
- {
- cout << root->_kv.first << "节点平衡因子不符合实际" << endl;
- return false;
- }
-
- // pRoot的左和右如果都是AVL树,则该树一定是AVL树
- return _IsBalanceTree(root->_left)
- && _IsBalanceTree(root->_right);
- }
-
- bool IsBalanceTree()
- {
- return _IsBalanceTree(_root);
- }
- #pragma once
- #include
-
- template<class K, class V>
- struct AVLTreeNode
- {
- pair
_kv; - AVLTreeNode
* _left; - AVLTreeNode
* _right; - AVLTreeNode
* _parent; -
- // 右子树-左子树的高度差
- int _bf;
-
- AVLTreeNode(const pair
& kv) - : _kv(kv)
- , _left(nullptr)
- , _right(nullptr)
- , _parent(nullptr)
- , _bf(0)
- {}
-
- // AVL树没有规定必须要设计平衡因子
- // 平衡因子只是一个实现的选择,方便控制平衡
- };
-
- template<class K, class V>
- class AVLTree
- {
- typedef AVLTreeNode
Node; - public:
- bool Insert(const pair
& kv) - {
- // 1.搜索树的规则插入
- // 2.看是否违反平衡规则,如果违反就需要处理:旋转
- if (_root == nullptr)
- {
- _root = new Node(kv);
- _root->_bf = 0;
- return true;
- }
-
- Node* parent = nullptr;
- Node* cur = _root;
- // 找到要插入的位置
- while (cur)
- {
- if (cur->_kv.first < kv.first)
- {
- parent = cur;
- cur = cur->_right;
- }
- else if (cur->_kv.first > kv.first)
- {
- parent = cur;
- cur = cur->_left;
- }
- else
- {
- return false;
- }
- }
-
- // 插入该元素
- cur = new Node(kv);
- if (parent->_kv.first < kv.first)
- {
- parent->_right = cur;
- }
- else
- {
- parent->_left = cur;
- }
-
- cur->_parent = parent;
-
- // 更新平衡因子
- // 最远要更新到根为止
- while (parent)
- {
- if (cur == parent->_right)
- {
- parent->_bf++;
- }
- else
- {
- parent->_bf--;
- }
-
- // 判断是否要继续更新
- // 1 or -1 -》 0 插入的节点填上了矮的那边
- if (parent->_bf == 0)
- {
- // 高度不变,更新结束
- break;
- }
- // 0 -》 1 or -1 插入节点导致一边变高了
- else if (parent->_bf == 1 || parent->_bf == -1)
- {
- // 子树的高度变了,继续更新祖先
- cur = cur->_parent;
- parent = parent->_parent;
- }
- // 1 or -1 -》 2 or -2 插入节点导致本来高的一边又变高了
- else if (parent->_bf == 2 || parent->_bf == -2)
- {
- // 子树不平衡 --需要旋转处理
- // 左单旋
- if (parent->_bf == 2 && cur->_bf == 1)
- {
- RotateL(parent);
- }
- // 右单旋
- else if (parent->_bf == -2 && cur->_bf == -1)
- {
- RotateR(parent);
- }
- // 左右双旋
- else if (parent->_bf == -2 && cur->_bf == 1)
- {
- RotateLR(parent);
- }
- // 右左双旋
- else if (parent->_bf == 2 && cur->_bf == -1)
- {
- RotateRL(parent);
- }
-
- break;
- }
- else
- {
- // 插入之前AVL就存在不平衡子树(平衡因子的绝对值>= 2的节点)
- assert(false);
- }
- }
-
- return true;
- }
- private:
- void RotateL(Node* parent)
- {
- Node* subR = parent->_right;
- Node* subRL = subR->_left;
-
- Node* ppNode = parent->_parent;
-
- parent->_right = subRL;
- parent->_parent = subR;
- if (subRL)
- {
- subRL->_parent = parent;
- }
- subR->_left = parent;
-
- if (parent == _root)
- {
- _root = subR;
- _root->_parent = nullptr;
- }
- else
- {
- if (ppNode->_left == parent)
- {
- ppNode->_left = subR;
- }
- else
- {
- ppNode->_right = subR;
- }
-
- subR->_parent = ppNode;
- }
-
- // 更新平衡因子
- subR->_bf = 0;
- parent->_bf = 0;
- }
-
- void RotateR(Node* parent)
- {
- Node* subL = parent->_left;
- Node* subLR = subL->_right;
-
- Node* ppNode = parent->_parent;
-
- parent->_left = subLR;
- parent->_parent = subL;
-
- if (subLR)
- {
- subLR->_parent = parent;
- }
- subL->_right = parent;
-
- if (parent == _root)
- {
- _root = subL;
- _root->_parent = nullptr;
- }
- else
- {
- if (parent == ppNode->_left)
- {
- ppNode->_left = subL;
- }
- else
- {
- ppNode->_right = subL;
- }
-
- subL->_parent = ppNode;
- }
-
- subL->_bf = 0;
- parent->_bf = 0;
- }
-
- void RotateLR(Node* parent)
- {
- Node* subL = parent->_left;
- Node* subLR = subL->_right;
- int bf = subLR->_bf;
-
- RotateL(parent->_left);
- RotateR(parent);
-
- // 更新平衡因子(分三种情况)
- if (bf == 0)
- {
- parent->_bf = 0;
- subL->_bf = 0;
- subLR->_bf = 0;
- }
- else if (bf == 1)
- {
- parent->_bf = 0;
- subL->_bf = -1;
- subLR->_bf = 0;
- }
- else if (bf == -1)
- {
- parent->_bf = 1;
- subL->_bf = 0;
- subLR->_bf = 0;
- }
- else
- {
- // subLR->_bf旋转前就有问题
- assert(false);
- }
- }
-
- void RotateRL(Node* parent)
- {
- Node* subR = parent->_right;
- Node* subRL = subR->_left;
- int bf = subRL->_bf;
-
- RotateR(parent->_right);
- RotateL(parent);
-
- // 更新平衡因子(分三种情况)
- if (bf == 0)
- {
- parent->_bf = 0;
- subR->_bf = 0;
- subRL->_bf = 0;
- }
- else if (bf == 1)
- {
- parent->_bf = -1;
- subR->_bf = 0;
- subRL->_bf = 0;
- }
- else if (bf == -1)
- {
- parent->_bf = 0;
- subR->_bf = 1;
- subRL->_bf = 0;
- }
- else
- {
- // subRL->_bf旋转前就有问题
- assert(false);
- }
- }
-
- bool _IsBalanceTree(Node* root)
- {
- // 空树也是AVL树
- if (nullptr == root)
- return true;
-
- // 计算pRoot节点的平衡因子:即pRoot左右子树的高度差
- int leftHeight = _Height(root->_left);
- int rightHeight = _Height(root->_right);
- int diff = rightHeight - leftHeight;
-
- // 如果计算出的平衡因子与pRoot的平衡因子不相等,
- // 或者pRoot平衡因子的绝对值超过1,则一定不是AVL树
- if (abs(diff) >= 2)
- {
- cout << root->_kv.first << "节点平衡因子异常" << endl;
- return false;
- }
-
- if (diff != root->_bf)
- {
- cout << root->_kv.first << "节点平衡因子不符合实际" << endl;
- return false;
- }
-
- // pRoot的左和右如果都是AVL树,则该树一定是AVL树
- return _IsBalanceTree(root->_left)
- && _IsBalanceTree(root->_right);
- }
- public:
- bool IsBalanceTree()
- {
- return _IsBalanceTree(_root);
- }
- private:
- Node* _root = nullptr;
- };