二叉搜索树
二叉搜索树的概念
二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
它的左右子树也分别为二叉搜索树
对二叉搜索树进行中序遍历可以得到有序的序列
AVL树
是一颗严格平衡搜索树通过控制左右子树的高度差,左右子树的高度之差的绝对值超过(-1/0/1)
我这里用的是三叉链,用平衡因子控制左右之差
三叉链可以更方便操作和理解,也是有弊的,每个结点在32为下指针为四个字节,但是利大于弊还是很不错的,方便
template<class K, class V>
struct AVLTreeNode //结点的定义
{
AVLTreeNode<K, V>* _left; //左子树
AVLTreeNode<K, V>* _right; //右子树
AVLTreeNode<K, V>* _parent; //父亲
int _bf;//平衡因子 balance factor
pair<K, V> _kv;
AVLTreeNode(const pair<K, V>& kv)
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_bf(0)
,_kv(kv)
{}
};
AVL树的精华在于它的插入,就主要讲这个方面
平衡时通过旋转来完成的,有四种旋转
1.左单旋
2.右单旋
3.左右双旋
4.右左双旋
这几点我们可以通过结点的平衡因子来判断时那种情况
a.在根的左边插入时–,右边时++,就是右节点-左节点
像这种的是10的结点右边插入了20,在右边插入所以10结点的平衡因子++而新增的点两边都是空,平衡因子是0
上插入的情况
//迭代插入,不用递归
pair<Node*, bool> Insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
return make_pair(_root, true);
}
//循坏插入
Node* parent = _root, *cur = _root;
while (cur)
{
//用pair的first比较
if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
else if(cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else
{
return make_pair(cur, false);
}
}
cur = new Node(kv);
Node* newnode = cur;
if (parent->_kv.first > cur->_kv.first)
{
parent->_left = cur;
cur->_parent = parent;
}
else
{
parent->_right = cur;
cur->_parent = parent;
}
while (parent)
{
//在左边父亲的bf--
if (parent->_left == cur)
{
parent->_bf--;
}
else //在右边bf++
{
parent->_bf++;
}
//更新完了如果父亲的bf变成0,就表示两边平衡了
if (parent->_bf == 0)
{
break;
}
else if (parent->_bf == 1 || parent->_bf == -1)
{
//parent所在的子树高度变了,会影响parent->parent
//继续往上更新
cur = parent;
parent = parent->_parent;
}//下面这些可以先不看,看图
else if (parent->_bf == 2 || parent->_bf == -2)
{
if (parent->_bf == -2)
{
if (cur->_bf == -1)
{
RotateR(parent);
}
else if(cur->_bf == 1)
{
RotateLR(parent);
}
}
else if (parent->_bf == 2)
{
if (cur->_bf == 1)
{
RotateL(parent);
}
else if (cur->_bf == -1)
{
RotateRL(parent);
}
}
break;
}
else
{
assert(false);
}
}
return make_pair(newnode, true);
}
1.左单旋
//左单旋
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
//把subR的左边给parent的右边
parent->_right = subRL;
//如果subRL不为空就把它的parent链接到parent
if (subRL)
subRL->_parent = parent;
//把subR的左边链接到parent
subR->_left = parent;
//保存parent的父结点
Node* parentparent = parent->_parent;
//改变parent的指向关系
parent->_parent = subR;
//判断parent是不是其他树的子结点
if (_root == parent)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
//这里就表示了是子结点,链接
subR->_parent = parentparent;
//判断parent是parentparent的左或右节点再链接subR
if (parentparent->_left == parent)
{
parentparent->_left = subR;
}
else
{
parentparent->_right = subR;
}
}
//更新平衡因子
parent->_bf = subR->_bf = 0;
}
2.右单旋
//右单旋
void RotateR(Node* parent)
{
//右单旋表明右结点高,往下调整,用parent->_left作为新的父结点
//subL的右结点做为parent->_left,再把subL->right = parent;subL的右结点链接parent
//parent->_left
Node* subL = parent->_left;
//parent->_left->_right -> subL->_right
Node* subLR = subL->_right;
parent->_left = subLR;
//可能为空, 更换父节点的指向
if (subLR)
{
subLR->_parent = parent;
}
//右节点链接parent
subL->_right = parent;
//保存祖父结点
Node* parentparent = parent->_parent;
//改变parent的父结点的指向
parent->_parent = subL;
//判断parent是不是子节点
if (_root == parent)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
subL->_parent = parentparent;
if (parentparent->_left == parent)
{
parentparent->_left = subL;
}
else
{
parentparent->_right = subL;
}
}
//更新平衡因子
parent->_bf = subL->_bf = 0;
}
这个图画的不太好,本人写博客就是当作笔记来写的比较随意,如果看不懂可以评论留言,在能力范围内的都会解答的
双旋
看见这个标题了吧,双旋比单旋难理解一点,但还行吧,不是很难,主要是控制平衡因子
双旋分为
a.左右双旋
b.右左双旋
两种情况
左右双旋
它的形状像是折线,而上面的单旋都是直线的形状
像这种的话就是要左右双旋来看个抽象图
来自他人的一张很棒的动图:
膜拜大佬好吧
虽然跟上面的新增位置不一样,但是本质是一样的,平衡因子的更新不同
具象图:
情况1:subLR->_bf == 1时的情况
情况二: subLR->_bf == -1时
情况三:subLR->_bf == 0时
右左双旋
情况一:
情况二:
情况三:
这些基本上就是AVL树的四种旋转了,可以自己画画图,理解理解