平衡二叉搜索树是具有某些特殊性质的二叉搜索树,对于每一个节点 p , 它的左右子树的高度相差不超过 1。将二叉排序树维持在平衡状态。因此查找,删除,插入的操作的时间复杂度维持在
O(log2n)
.
查找元素,遍历,找最大最小值,求树的高度,这些基本操作和普通的二叉搜索树几乎一样,直接借用二叉搜索树的成员函数,具体实现何以参见
二叉搜索树
对其进行插入元素时,首先按照普通的二叉搜索树插入,但是插入元素之后可能导致叶子节点的高度差大于1,这时候需要进行节点的旋转,恢复平衡二叉树的性质。
如下图插入键值为 7 节点,很显然新的排序二叉树的不再满足平衡二叉树的性质。
旋转
由于任意节点最多有两个儿子,高度不平衡时,此节点的两颗子树的高度相差2,有以下几种情况:
case1:左左情况 13 节点的左子树 8节点 的高度比右子树 15节点 的高度大 2, 左子树 8节点的 左子树 5节点 的高度大于 右子树 10节点
case2:左右情况 13 节点的左子树 8节点 的高度比右子树 15节点 的高度大 2, 左子树 8节点的 右子树 10节点 的高度大于 右子树 5节点
case3:右左情况 9 节点的右子树 15节点 的高度比左子树 8节点 的高度大 2, 右子树 15节点的 左子树 12节点 的高度大于 右子树 18节点
case3:右右情况 9 节点的右子树 15节点 的高度比左子树 8节点 的高度大 2, 右子树 15节点 的右子树 18节点 的高度大于 左子树 12节点
单旋转:
case1 和 case4 属于对称情况,只需要一次旋转就可以,下面分析case1 左左情况 singRotateLeft(k1) :(节点k1 不符合平衡二叉树的性质)
由对称性对于case4执行 singRotateRight(K1)。
void avlTree::singRotateLeft(Node *p) // case1:左左情况左旋
{
Node *pLchild = p->lchild;
this->transplant(p, pLchild);
p->lchild = pLchild->rchild;
if (pLchild->rchild) // 可能待左转节点的左孩子没有右孩子
{
pLchild->rchild->parent = p;
}
pLchild->rchild = p;
p->parent = pLchild;
}
void avlTree::singRotateRight(Node *p) // case4:右右情况
{
Node *pRchild = p->rchild;
this->transplant(p, pRchild);
p->rchild = pRchild->lchild;
if (pRchild->lchild) // 可能待右转节点的右孩子没有左孩子
{
pRchild->lchild->parent = p;
}
pRchild->lchild = p;
p->parent = pRchild;
}
// 移植节点
void transplant(Node* oldNode, Node* newNode); // 移植节点
参见二叉搜索树 里的具体实现。
http://blog.youkuaiyun.com/lmx2014001/article/details/52072784
双旋转
case2 和 case3 属于对称情况,下面分析 case 2:(节点k1 不符合平衡二叉树的性质)
先以节点 k2 = k1-> lchild为研究对象进行一次 右右情况的旋转,singRotateRight(Node *p),将问题转化为 case1,然后在case1 情况下,再经过左左情况的旋转 singRotateLeft(k1)。
void avlTree::doubleRotateLR(Node *p) // case2:左右情况
{
this->singRotateRight(p->lchild);
this->singRotateLeft(p);
}
void avlTree::doubleRotateRL(Node *p) // case3:右左情况
{
this->singRotateLeft(p->rchild);
this->singRotateRight(p);
}
插入节点
插入节点分两步:
第一步,和普通的二叉排序树相同的步骤插入;
第二步,从插入新节点的父亲开始,沿着走向根节点的简单路径,不断检查是否满足平衡二叉树的性质。
在描述具体检测步骤之前,先引入 任意节点 p 的平衡因子,即该节点 p 的左子树高度与右子树高度之差,具体实现如下:
易知
p−>getBFac()
可能的返回值为 2, 1, 0, -1, -2,当取值为 2 和 - 2时不满足平衡性质。
int Node::getBFac()
{
return this->lchild->getHight() - this->rchild->getHight();
}
详细检查及恢复步骤:
1. 求得新插入节点 N 的的父亲结点 p 的平衡因子
p−>getBFac()
,可能取值只可能为 1,0,-1,图解如下:
case1,2不会改变以 父亲结点 p 为根节点的子树的高度,因此不会破坏整棵树的平衡性质。不需要进一步检测!
case3,4使父亲结点 p 为根节点的子树的高度由 1 (只有一个根节点 p)增加到了 1,因此可能破坏整棵树的平衡性质。需要进行下一步检测。
新插入节点N的父亲节点 p 的平衡因子为 1 时,进一步检查 节点N 的祖父节点G,如果 G−>getBFac() 为2 或者 -2 ,则找到问题节点,并对其进行旋转操作,不再继续向上检测问题节点;如果 G−>getBFac()=0 时才可以确定新插入节点并没有影响整棵树的平衡性质,则停止检查,不需要维护了; G−>getBFac() 为1 或者 -1, 则将可能出现的问题点上移。
不断重复步骤 2,直到不断上升的祖父节点 G 越过根节点变为 NULL。
void avlTree::insertAvlNode(int newValue)
{
Node *newNode = new Node(newValue);
Node *p, *tempParent = new Node(), *tempGrandpa = new Node(), *tempuncle = new Node();
int parentFac, GrandpaFac;
if (this->getSize() == 0)
{
this->root = newNode;
}
else
{
p = this->root;
while (p)
{
// 保存父亲结点,万一他的孩子为空,跳出循环后还可以翻出来
tempParent = p;
if (newValue > p->data)
{
p = p->rchild;
}
else
{
p = p->lchild;
}
}
newNode->parent = tempParent;
if (newValue > tempParent->data)
{
tempParent->rchild = newNode;
}
else
{
tempParent->lchild = newNode;
}
// 以上步骤各普通的二叉排序树插入节点一样
parentFac = tempParent->getBFac();
if (parentFac) // 新插入节点的父节点 tempParent 平衡因子为非零,则tempParent 的高度肯定增加了 1,由原先的 0 变为1
{
int rFlag = 0; // 标记是否旋转,最多只需要旋转一次(doubleRotate 也算一次)
tempGrandpa = tempParent->parent;
while (tempGrandpa && !rFlag)
{
GrandpaFac = tempGrandpa->getBFac();
if (!GrandpaFac)
{
break;
}
if (GrandpaFac == 2) //左
{
if (parentFac == 1) // 左左
{
singRotateLeft(tempGrandpa);
}
else // 左右
{
doubleRotateLR(tempGrandpa);
}
rFlag = 1;
}
if (GrandpaFac == -2) //右
{
if (parentFac == 1) // 右左
{
singRotateLeft(tempGrandpa);
}
else // 右右
{
singRotateRight(tempGrandpa);
}
rFlag = 1;
}
if (!rFlag) // 把可能出现问题的节点往上移动
{
tempParent = tempGrandpa;
parentFac = GrandpaFac;
tempGrandpa = tempParent->parent;
}
}
}
}
}
欢迎批评指正 !