赞
踩
目录
首先,我们要了解什么是二叉搜索树,这里大家可以看我写的上一篇博客 二叉搜索树
并且本文也在二叉搜索树的基础上进行讲解。
我们可以知道,AVL树是二叉搜索树的一种,它与二叉搜索树的不同之处就在于平衡二字,这也是为了弥补二叉搜索树的缺陷而做出的改变。
我们知道,二叉搜索树在极端条件下会出现下面的情况:
在这种情况下,二叉搜索树退化为链表,无论是查找,插入,删除等一系列操作,二叉搜索树都与链表没有任何区别,那我们建立二叉搜索树的优势就不复存在了。
为了弥补这个缺点,有了二叉平衡搜索树,二叉平衡搜索树有如下性质:
1.满足二叉搜索树的性质
2.任何一个节点的左右树高度差不超过一
3.左右子树都满足AVL树的性质
这就保证了二叉搜索树的性质同时不会出现极端情况退化为链表,满足这种条件的树我们称之为二叉平衡搜索树。
首先,我们知道AVL树是二叉搜索树的一种,唯一多了平衡的特性,因此结点结构也是与二叉搜索树类似的,只是多了一个 int 类型的数据 ,平衡因子 _bf ,平衡因子的值是此结点的右子树高度减去左子树高度后得到的值,具体结构如下:
- template<class T>
- struct AVLTreeNode
- {
- AVLTreeNode(const T& data = T())
- : _pLeft(nullptr)
- , _pRight(nullptr)
- , _pParent(nullptr)
- , _data(data)
- , _bf(0)
- {}
-
- AVLTreeNode<T>* _pLeft;
- AVLTreeNode<T>* _pRight;
- AVLTreeNode<T>* _pParent;
- T _data;
- int _bf; // 节点的平衡因子
- };

使用template类模板我们可以使用多种数据类型的AVL结点类型,如果想要存储key-value结构的数据,我们可以将data的数据类型更改为pair ,这里为了方便大家理解,我采用key的结构,同时我们写了一个构造函数来进行结点的构造。
完成了AVL树的结点结构,我们要进行AVL树的模板类的编写,首先,我们可以搭建框架:
- template<class T>
- class AVLTree
- {
- typedef AVLTreeNode<T> Node;
- public:
- AVLTree()
- : _pRoot(nullptr)
- {}
-
- private:
- Node* _pRoot;
- };
这里我们知道AVL树的类模板中只有一个成员变量 _pRoot,用来存储AVL树的的根结点,并且我们简单写一个构造函数,有兴趣的朋友也可以去实现一下拷贝构造,析构......默认成员函数。
接下来我们编写AVL类中主要的成员方法:
与二叉搜索树不同的是,AVL的树操作更加复杂,首先,我们要找到结点的插入位置,这步操作我们可以与二叉搜索树的插入操作进行复用,其次,我们插入结点后,要让树满足 “平衡” ,这里我们就有可能要对树的结构进行一些改变,我们称之为 旋转操作。
首先,我们要知道,在插入之前树是一棵AVL树,总的来说,这棵树只有三种情况:
1.左右均衡
这种情况下,AVL树几完全平衡的,此时无论我们在哪边插入都不会影响AVL的平衡性质
2.左高右低
在AVL树中,左右高度差不超过1,因此即便左高右低,高度也只差了1,情况如下:
这种情况下,我们如果插入结点到右子树,会使AVL树更加平衡,但是如果插入节点到左子树上,就会影响AVL的平衡,并且插入到左子树也有两种可能:
1.插入到左结点的 左子树上
2.插入到左结点的右子树上
这两种操作需要的操作也是不相同的,我们后面再说。
3.左低右高
这种情况下,我们如果插入结点到左子树,会使AVL树更加平衡,但是如果插入节点到右子树上,就会影响AVL的平衡,并且插入到右子树也有两种可能:
1.插入到右结点的左子树上
2.插入到右结点的右子树上
并且我们要知道插入结点后的一些性质:
1.插入结点后,只对其祖先结点产生影响。
2.若某祖先结点的平衡因子变为0,影响不再向上传递。
3.若某祖先的平衡因子为+1/-1,影响继续向上传递。
当右子树偏高且结点插入到右节点的右子树上时候,我们要采用左旋操作,详细过程如下:
首先,我们要注意,只有当AVL树本来就右边偏高并且插入结点在AVL树的右边子树上才能够使用左旋,把这个条件变化成数据体现,其实也就是当我们找到平衡因子异常的结点parent,如果它的平衡因子为2,且cur平衡因子为1。类似这样的结构,parent,cur和插入结点在一条直线上,如下:
满足了这个条件,我们就可以以cur为轴心进行左旋,并且我们可以注意到,左旋后的parent和cur平衡因子都变为0,则影响不再向上传递。
也就是说,满足左旋场景我们只需要左旋一次就能够完成调整,得到的树就满足AVL树的条件。
代码如下:
- // 左单旋
- void RotateL(Node* pParent)
- {
- Node* cur = pParent->_pRight;
- Node* curleft = cur->_pLeft;
-
- //左旋第一步,cur的左树变为parent的右子树
- pParent->_pRight = curleft;
- if (curleft)
- {
- curleft->_pParent = pParent;
- }
-
- //左旋第二步,parent变为cur的左子树
- cur->_pLeft = pParent;
-
- //记录根节点的上一个节点
- Node* ppnode = pParent->_pParent;
-
- pParent->_pParent = cur;
-
- if (pParent == _pRoot)//如果parent已经是根节点了
- {
- _pRoot = cur;
- cur->_pParent = nullptr;
- }
- else //如果parent不是根节点,需要把cur与前面节点进行连接
- {
- if (ppnode->_pLeft == pParent)//如果原来的parent是上一节点的左孩子
- {
- ppnode->_pLeft = cur;
- cur->_pParent = ppnode;
- }
- else //如果原来的parent是上一节点的右孩子
- {
- ppnode->_pRight = cur;
- cur->_pParent = ppnode;
- }
- }
-
- //完成平衡因子的修改
- cur->_bf = pParent->_bf = 0;
-
-
- }

当左子树偏高且结点插入到左节点的左子树上时候,我们要采用右边旋操作,详细过程如下:
要进行右旋 ,需要满足的条件为:
插入之前,左子树偏高,插入结点在左结点的左子树上,和左旋一样,parent,cur和插入结点在一条直线上,不过类似与下面的结构:
描述为数据表现就是,当我们找到平衡因子异常的结点parent:若parent的bf为2,cur的bf为1,我们就以cur为轴心进行右旋。
并且我们可以注意到,进行旋转后,parent和cur的平衡因子变为0,影响不再向上传递,
也就是说,满足右旋场景我们只需要右旋一次就能够完成调整,得到的树就满足AVL树的条件。
代码如下:
- // 右单旋
- void RotateR(Node* pParent)
- {
- Node* cur = pParent->_pLeft;
- Node* curright = cur->_pRight;
-
- //右旋第一步,cur的右子树给parent的左子树
- pParent->_pLeft = curright;
- if (curright)
- {
- curright->_pParent = pParent;
- }
-
- //右旋第二步,parent变为cur的右子树
- cur->_pRight = pParent;
- Node* ppnode = pParent->_pParent;
- pParent->_pParent = cur;
-
- if (pParent == _pRoot)
- {
- _pRoot = cur;
- cur->_pParent = nullptr;
- }
- else
- {
- if (ppnode->_pLeft == pParent)
- {
- ppnode->_pLeft = cur;
- cur->_pParent = ppnode;
- }
- else
- {
- ppnode->_pRight = cur;
- cur->_pParent = ppnode;
- }
- }
- cur->_bf = pParent->_bf = 0;
-
- }

1.右左双旋
在讲解插入情况时,我们知道右高左低有两种情况
其中,我们如果插入到右结点的右子树,就满足了左旋的条件,直接进行左旋就可以完成。
但是我们如果插入到右结点的左子树,这个时候 ,parent ,cur和插入结点并不在一条直线上,而是一条折线结构,数据表现为,当我们找到平衡因子异常的结点parent,其平衡因子为2,cur的平衡因子为-1,其右如下:
详细操作如下:
首先,我们找到平衡因子异常的结点为parent,先对其right结点进行右旋,然后再对parent结点进行左旋,具体操作如下:
- // 右左双旋
- void RotateRL(Node* pParent)
- {
- RotateR(pParent->_pRight);
- RotateL(pParent);
- }
2.左右双旋
与右左双旋相对的是,左右双旋是左高右低中的一种情况
当我们把新结点插入到左结点的左子树时,满足右旋操作的条件,进行右旋操作即可。
但是如果我们插入到 左结点的右子树时,形成另外一种折线结构,数据表现为,当我们找到平衡因子异常的结点parent,其平衡因子为-2,cur的平衡因子为1,如下:
此时,我们需要进行左右双旋,具体操作如下:
代码如下:
- // 左右双旋
- void RotateLR(Node* pParent)
- {
- RotateL(pParent->_pLeft);
- RotateR(pParent);
- }
插入操作全过程:
- // 在AVL树中插入值为data的节点
- bool Insert(const T& data)
- {
- //树为空
- if (!_pRoot)
- {
- Node* newnode = new Node(data);
- _pRoot = newnode;
- return true;
- }
-
- Node* parent =nullptr;
- Node* cur = _pRoot;
- while (cur)
- {
- if (cur->_data > data)
- {
- parent = cur;
- cur = cur->_pLeft;
- }
- else if (cur->_data < data)
- {
- parent = cur;
- cur = cur->_pRight;
- }
- else
- {
- return false;
- }
- }
- Node* newnode = new Node(data);
- if (parent->_data < data)
- {
- parent->_pRight = newnode;
- newnode->_pParent = parent;
- }
- else
- {
- parent->_pLeft = newnode;
- newnode->_pParent = parent;
- }
- //向添加节点的祖先进行进行平衡因子的更新,查找是否有_bf>1的节点
- cur = newnode;
- parent = newnode->_pParent;
- while (parent)
- {
- if (cur == parent->_pLeft)
- {
- parent->_bf--;
- }
- else
- {
- parent->_bf++;
- }
-
- //如果出现平衡因子为0,更新结束
- if (parent->_bf == 0)
- {
- break;
- }
- else if (parent->_bf ==1|| parent->_bf == -1)//如果平衡因子正常,继续向上进行更新
- {
- cur = parent;
- parent = parent->_pParent;
- }
- 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)
- {
- RotateRL(parent);
- }
- else if (parent->_bf == -2 && cur->_bf == 1)
- {
- RotateLR(parent);
- }
-
- break;
- }
- else //平衡因子大于2,出现问题
- {
- assert(false);
- }
- }
- return true;
- }

检查AVL树是否平衡,我们需要计算左右子树的高度,使用递归操作可以简单实现,操作如下:
- public:
- bool IsBalance()
- {
- return IsBalance(_pRoot);
- }
-
-
- int Height()
- {
- return Height(_pRoot);
- }
-
-
- //这样做的目的是为了让外部可以直接调用私有成员_pRoot
-
-
- private:
- int Height(Node* root)
- {
- if (root == nullptr)
- return 0;
-
- int leftHeight = Height(root->_pLeft);
- int rightHeight = Height(root->_pRight);
-
- return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
- }
-
-
- bool IsBalance(Node* root)
- {
- if (root == nullptr)
- return true;
-
- int leftHight = Height(root->_pLeft);
- int rightHight = Height(root->_pRight);
-
- if (rightHight - leftHight != root->_bf)
- {
- std::cout << "平衡因子异常:" << root->_data << "->" << root->_bf << std::endl;
- std::cout << leftHight << " "<<rightHight << " " << root->_bf;
- return false;
- }
-
- return abs(rightHight - leftHight) < 2
- && IsBalance(root->_pLeft)
- && IsBalance(root->_pRight);
- }

最后,其实AVL树的删除,查找,删除等等操作我没有进行实现,因为其实只要掌握了插入,其他操作简简单单,如果我完成了AVL树的其他功能,我也会添加进来。
下面贴上完整代码供大家参考:
- #pragma once
- #include<cassert>
- #include<iostream>
-
- template<class T>
- struct AVLTreeNode
- {
- AVLTreeNode(const T& data = T())
- : _pLeft(nullptr)
- , _pRight(nullptr)
- , _pParent(nullptr)
- , _data(data)
- , _bf(0)
- {}
-
- AVLTreeNode<T>* _pLeft;
- AVLTreeNode<T>* _pRight;
- AVLTreeNode<T>* _pParent;
- T _data;
- int _bf; // 节点的平衡因子
- };
-
-
- // AVL: 二叉搜索树 + 平衡因子的限制
- template<class T>
- class AVLTree
- {
- typedef AVLTreeNode<T> Node;
- public:
- AVLTree()
- : _pRoot(nullptr)
- {}
-
- // 在AVL树中插入值为data的节点
- bool Insert(const T& data)
- {
- //树为空
- if (!_pRoot)
- {
- Node* newnode = new Node(data);
- _pRoot = newnode;
- return true;
- }
-
- Node* parent =nullptr;
- Node* cur = _pRoot;
- while (cur)
- {
- if (cur->_data > data)
- {
- parent = cur;
- cur = cur->_pLeft;
- }
- else if (cur->_data < data)
- {
- parent = cur;
- cur = cur->_pRight;
- }
- else
- {
- return false;
- }
- }
- Node* newnode = new Node(data);
- if (parent->_data < data)
- {
- parent->_pRight = newnode;
- newnode->_pParent = parent;
- }
- else
- {
- parent->_pLeft = newnode;
- newnode->_pParent = parent;
- }
- //向添加节点的祖先进行进行平衡因子的更新,查找是否有_bf>1的节点
- cur = newnode;
- parent = newnode->_pParent;
- while (parent)
- {
- if (cur == parent->_pLeft)
- {
- parent->_bf--;
- }
- else
- {
- parent->_bf++;
- }
-
- //如果出现平衡因子为0,更新结束
- if (parent->_bf == 0)
- {
- break;
- }
- else if (parent->_bf ==1|| parent->_bf == -1)//如果平衡因子正常,继续向上进行更新
- {
- cur = parent;
- parent = parent->_pParent;
- }
- 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)
- {
- RotateRL(parent);
- }
- else if (parent->_bf == -2 && cur->_bf == 1)
- {
- RotateLR(parent);
- }
-
- break;
- }
- else //平衡因子大于2,出现问题
- {
- assert(false);
- }
- }
- return true;
- }
-
- // AVL树的验证
- bool IsBalance()
- {
- return IsBalance(_pRoot);
- }
-
-
- int Height()
- {
- return Height(_pRoot);
- }
-
-
- private:
-
- // 根据AVL树的概念验证pRoot是否为有效的AVL树
-
- int Height(Node* root)
- {
- if (root == nullptr)
- return 0;
-
- int leftHeight = Height(root->_pLeft);
- int rightHeight = Height(root->_pRight);
-
- return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
- }
-
-
- bool IsBalance(Node* root)
- {
- if (root == nullptr)
- return true;
-
- int leftHight = Height(root->_pLeft);
- int rightHight = Height(root->_pRight);
-
- if (rightHight - leftHight != root->_bf)
- {
- std::cout << "平衡因子异常:" << root->_data << "->" << root->_bf << std::endl;
- std::cout << leftHight << " "<<rightHight << " " << root->_bf;
- return false;
- }
-
- return abs(rightHight - leftHight) < 2
- && IsBalance(root->_pLeft)
- && IsBalance(root->_pRight);
- }
-
- // 右单旋
- void RotateR(Node* pParent)
- {
- Node* cur = pParent->_pLeft;
- Node* curright = cur->_pRight;
-
- //右旋第一步,cur的右子树给parent的左子树
- pParent->_pLeft = curright;
- if (curright)
- {
- curright->_pParent = pParent;
- }
-
- //右旋第二步,parent变为cur的右子树
- cur->_pRight = pParent;
- Node* ppnode = pParent->_pParent;
- pParent->_pParent = cur;
-
- if (pParent == _pRoot)
- {
- _pRoot = cur;
- cur->_pParent = nullptr;
- }
- else
- {
- if (ppnode->_pLeft == pParent)
- {
- ppnode->_pLeft = cur;
- cur->_pParent = ppnode;
- }
- else
- {
- ppnode->_pRight = cur;
- cur->_pParent = ppnode;
- }
- }
- cur->_bf = pParent->_bf = 0;
-
- }
-
-
-
- // 左单旋
- void RotateL(Node* pParent)
- {
- Node* cur = pParent->_pRight;
- Node* curleft = cur->_pLeft;
-
- //左旋第一步,cur的左树变为parent的右子树
- pParent->_pRight = curleft;
- if (curleft)
- {
- curleft->_pParent = pParent;
- }
-
- //左旋第二步,parent变为cur的左子树
- cur->_pLeft = pParent;
-
- //记录根节点的上一个节点
- Node* ppnode = pParent->_pParent;
-
- pParent->_pParent = cur;
-
- if (pParent == _pRoot)//如果parent已经是根节点了
- {
- _pRoot = cur;
- cur->_pParent = nullptr;
- }
- else //如果parent不是根节点,需要把cur与前面节点进行连接
- {
- if (ppnode->_pLeft == pParent)//如果原来的parent是上一节点的左孩子
- {
- ppnode->_pLeft = cur;
- cur->_pParent = ppnode;
- }
- else //如果原来的parent是上一节点的右孩子
- {
- ppnode->_pRight = cur;
- cur->_pParent = ppnode;
- }
- }
-
- //完成平衡因子的修改
- cur->_bf = pParent->_bf = 0;
-
-
- }
-
- // 右左双旋
- void RotateRL(Node* pParent)
- {
- RotateR(pParent->_pRight);
- RotateL(pParent);
- }
-
- // 左右双旋
- void RotateLR(Node* pParent)
- {
- RotateL(pParent->_pLeft);
- RotateR(pParent);
- }
-
- private:
- Node* _pRoot;
- };
-
-

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。