二叉搜索树
概念
二叉搜索树又称为二叉排序树:
(1)或为空树;
(2)若左子树不空,则左子树上所有节点的值都小于根节点的值;
(3)若右子树不空,则右子树上所有节点的值都大于根节点的值;
(4)它的左右子树也分别是二叉搜索树
例如:
对于给定数组 int a [] = {5,3,4,1,7,8,2,6,0,9}; 可以建立二叉搜索树如下:
二叉搜索树的操作
一、二叉搜索树的查找
(1)若根节点为空,查找失败;
(2)若根节点非空----树非空
例如:
在下列二叉搜索树中查找节点值等于 2 的节点:
要查找的节点值为 data == 2
根节点 root->val==5 > data ,则应该在该二叉搜索树的左子树中进行查找…
若根节点非空:
(1)若根节点的值 key == 要查找的节点值 key,则直接返回该节点信息 true;
(2)若根节点的值 key < 要查找的节点值 key,则在其右子树中继续进行查找;
(3)若根节点的值 key > 要查找的节点值 key,则在其左子树中继续进行查找;
否则,没有找到,返回 false
二、二叉搜索树的插入
(1)若要进行插入的二叉搜索树为空,则直接进行插入;
(2)若要进行插入的二叉搜索树非空,则需要按二叉搜索树的性质,首先查找要进行插入的位置,然后插入新的节点信息;
三、二叉搜索树的删除
(1)若二叉搜索树为空,则删除失败 false;
(2)在二叉搜索树中查找要进行删除的节点是否存在,若存在进行删除,不存在返回 false;
若要进行删除的节点存在,则可能有以下三种情况:
1)要删除的节点是叶子节点--------直接进行删除;
2)要删除的节点有一个孩子-------删除该节点之后,其双亲结点指向删除节点的孩子节点;
3)要删除的节点有两个孩子------在要进行删除的节点的左子树中寻找值最大值的节点来填补被插入节点位置;或是在删除节点的右子树中寻找值最小的节点来填补被插入节点位置
删除节点之后,要确保二叉搜索树的特性不变------左子树节点值 < 根节点值 < 右子树节点值(即中序遍历结果为有序)
二叉搜索树相关代码参考:
添加链接描述
二叉搜索树的性能分析
上述我们讨论的插入删除节点时候,都需要首先进行查找操作,因此查找效率代表了二叉搜索树中各个操作的性能;
对有 N 个节点的二叉搜索树,若每个元素查找的概率相同,则二叉搜索树平均查找长度是节点在二叉搜索树的深度的函数,即节点越深比较次数越多。
但对于同一个节点值的集合,若各个值插入的次序不同,则会得到不同结果的二叉搜索树:
那么二叉搜索树的查找效率也会发生变化
显然,右边单支树的查找效率会更差一些,为提升二叉搜索树的查找效率,我们就考虑能否使二叉搜索树的深度减少来实现?
这就引入了 AVL 平衡二叉树的基本概念
AVL 树
概念
一颗 AVL 树:
(1)或是空树;
(2)它的左右子树都是 AVL 树;
(3)左右子树高度之差(平衡因子)的绝对值不超过1 (-1、0、1)
若一颗二叉搜索树是高度平衡的,它就是 AVL 树,若它有 N 个节点,则其高度可以保持在 0(log2(N)),搜索时间复杂度 0(log2(N))
AVL 树就是在二叉搜索树的基础上增加了平衡因子,因此 AVL 树也可以看作是二叉搜索树
AVL 树的节点定义
因为需要由下往上计算节点的平衡因子,因此需要保存每个节点的双亲结点的位置,故采用孩子双亲表示法,并引入平衡因子的定义:
template<class T>
struct AVLTreeNode {
AVLTreeNode<T>* _left;
AVLTreeNode<T>* _right;
AVLTreeNode<T>* _parent;
T _data;
int _bf; //节点的平衡因子
AVLTreeNode(const T& data = T())
:_left(nullptr), _right(nullptr), _parent(nullptr)
,_data(data),_bf(0)
{}
};
AVL 树的插入
首先按照二叉搜索树的方式插入新节点信息;
其次调整节点的平衡因子大小;
若平衡因子不满足 AVL 树的要求(-1,0,1)则需要对该二叉搜索树进行旋转调整,使其平衡因子满足AVL树的条件
右单旋
当新插入的节点在较高左子树的左侧时--------需要进行右单旋:
(1)插入节点值为 5.5:
(2)插入节点值为 6.5:
总结:当在 AVL 树的较高左子树的左侧(作为左孩子或右孩子插入)插入新节点时候,需要对该二叉搜索树进行右单旋调整为 AVL 树
左单旋
当新插入的节点在较高右子树的右侧时--------需要进行左单旋:
(1)插入节点值为 8.5:
(2)插入节点值为 10:
总结:当在 AVL 树的较高右子树的右侧(作为左孩子或右孩子插入)插入新节点时候,需要对该二叉搜索树进行左单旋调整为 AVL 树
左右双旋
当新插入的节点位于较高左子树的右侧时--------需要进行双旋调整,并且先进行左旋,后进行右旋
(1)插入新节点之后不需要进行调整:
(2)插入节点值为 7.5:
(3)插入节点值为 8.5:
总结:当在 AVL 树的较高左子树的右侧(作为左孩子或右孩子插入)插入新节点时候,需要对其进行左右双旋调整为 AVL 树
右左双旋
当新插入的节点位于较高右子树的左侧时--------需要进行双旋调整,并且先进行右旋,后进行左旋
(1)插入不需要进行调整:
(2)插入节点值为 7.3:
(3)插入节点值为 7.8:
总结:当在 AVL 树的较高右子树的左侧(作为左孩子或右孩子插入)插入新节点时候,需要对其进行右左双旋调整为 AVL 树
AVL 树的验证
AVL 树是在二叉搜索树的基础上增加了平衡性的限制,因此要验证 AVL 树:
首先需要验证其是二叉搜索树,即中序遍历结果为有序序列;
其次需要验证其平衡因子的计算是否正确,且平衡因子大小不超过 1 (-1,0,1)
计算二叉搜索树的高度:
int _Height(Node* root)
{
if (nullptr == root)
return 0; //空树高度为 0
int leftHeight = _Height(root->_left); //左子树高度
int rightHeight = _Height(root->_right); //右子树高度
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
//返回左右子树高度较高的值,再加上根节点一层
}
判断其平衡因子是否满足条件:
bool _IsValidAVLTree(Node* root)
{ //检测平衡因子是否正确
if (nullptr == root) //空树是 AVL 树
return true;
int leftHeight = _Height(root->_left); //左子树高度
int rightHeight = _Height(root->_right); //右子树高度
//若左右子树高度只差的绝对值大于 1 或者是二叉树节点中定义的平衡因子与实际计算所得的平衡因子不相等,则表明不是 AVL 树
if (ads(rightHeight - leftHeight) > 1 || (rightHeight - leftHeight) != root->_bf)
return false;
//递归判断其左右子树,AVL树的左右子树都应该是 AVL 树
return _IsValidAVLTree(root->_left) && _IsValidAVLTree(root->_right);
}
AVL 树相关代码参考:
二叉搜索树 以及 AVL 树的插入过程都需要考虑很多的因素,因此读者应该注重理解插入时候的操作过程,然后再参考代码理解细节
一起加油吧!!
(代码是自己敲的,可能会有存在问题的情况,欢迎评论指出哦~)