什么是二叉搜索树
二叉搜索树也叫二叉排序树或二叉查找树
二叉搜索树,一棵二叉树可以为空也可以不空,如果不为空就满足以下性质:
- 非空左子树的所有键值小于其根结点的键值
- 非空右子树的所有键值大于其根结点的键值
- 左、右子树都是二叉搜索树
二叉搜索树操作的特别函数
Position Find(ElementType X,BinTree BST)
/*从二叉搜索树BST中查找元素X,返回其所在结点的地址*/
Position FindMin(BinTree BST)
/*从二叉搜索树BST中查找并返回最小元素所在结点的地址*/
Position FindMax(BinTree BST)
/*从二叉搜索树BST中查找并返回最大元素所在结点的地址*/
查找操作
从根节点开始,如果树为空,返回NULL
若搜索树非空,则根节点关键字与X进行比较,并进行不同处理:
- 若X小于根结点键值,只需在左子树中继续搜索
- 若X大于根结点键值,在右子树中进行继续搜索
- 若两者比较结果相等搜索完成,返回指向此结点的指针
Poosition Find(ElementType X,BinTree BST)
{
if(!BST) return NULL;/*查找失败*/
if(x > BST->Data)/*如果大于根结点键值*/
{
return Find(X,BST->Right);/*在右子树中继续查找*/
}
else if(X < BST->Data)/*如果小于根结点键值*/
{
return Find(X,BST->Left);/*在左子树中继续查找*/
}
else/*找到了*/
{
returnn BST;/*返回结点所在地址*/
}
}
以上是尾递归效并不高,因此可将其换成非递归函数,即换为迭代函数
Position IterFind(ElementType X,BinTree BST)
{
while(BST)/*如果树不为空*/
{
if(X > BST->Data)/*如果大于根结点键值*/
{
BST = BST->Right;/*向右子树移动继续查找*/
}
else if(X <BST->Data)/*如果大于根结点键值*/
{
BST = BST->Left;/*向左子树移动继续查找*/
}
else
{
return BST;/*查找成功,返回结点的地址*/
}
return NULL;/*查找失败*/
}
查找效率决定于树的高度
查找最大和最小元素
- 最大元素一定在树的最右分枝的端结点上
- 最小元素一定在树的最左分枝的端结点上
查找最小元素的递归函数
Position FindMin(BinTree BST)
{
if(!BST) return NULL;/*空的二叉搜索树,返回NULL*/
else if(!BESt->Left)
{
return BST;/*找到最左叶结点并返回*/
}
else
{
return FindMin(BST->Left);
}
}
查找最大元素的迭代函数
Position FindMax(BinTree BSST)
{
if(BST)
{
while(BST->Right)
{
BST = BSt->Right;
}
}
return BST;
}
二叉搜索树的插入
关键是要找到元素应该插入的位置,即插入后还是一个二叉搜索树
可以采用与Find类似的方法
不过与Find不同的是,由于需要插入元素,所以当找到可以插入的位置时,这个位置一定是空的,所以要重新申请一个空间插入并返回插入的地址。返回上一层时将插入的地址赋值给上一层的Left 或 Right;
BinTree Insert(ElementType X,BinTree BST)
{
if(!BST)/*若原树为空生成 并返回一个结点的二叉搜索树*/
{
BST= malloc(sizeof(struct TreeNode));
BST->Data = X;
BST->Left = BSt->Right = NULL;
}
else
{
if(X < BST->Data)
{
BST->Left = Insert(X,BST->Left);/*递归插入左子树*/
}
else if(x > BST->Data)
{
BST->Right = Insert(X,BST->Right)/*递归插入右子树*/
}
return BST;
}
二叉搜索树的删除
- 要删除的是叶结点:直接删除,并修改父结点Left or Right指针置为NULL
- 要删除的结点为只有一个孩子的结点,将该结点的父结点的指针指向要删除的结点的孩子,即爷爷结点指向孙子结点
- 要删除的结点有左右两棵子树:用另一个结点替代被删除结点:右子树的最小元素或者左子树的最大元素(因为左子树的最大元素一定在左子树的最右边,右子树的最小元素一定在右子树的最左边)
BinTree Delete(ElementType X,BinTree BST)
{
Position Tmp;
if(!BST) printf("要删除的元素未找到");/*树为空*/
else if(x < BST->Data)
BST->Left = Delete(X,BST->Left);/*左子树递归删除*/
else if(X > BST->Data)
BST->Right = Delete(X,BST->Right);/*右子树递归删除*/
else/*找到要删除的结点*/
{
if(BST->Left && BST->Right)/*被删除的结点有左右两个孩子*/
{
Tmp = FindMin(BST->Right);/*在右子树中找到最小元素填充删除结点*/
BST->Data = Tmp->Data;
BST->Riht = Delete(BST->Data,BSt->Right);/*在删除结点的右子树中删除最小元素*/
}
else/*被删除的结点有一个孩子或没有孩子*/
{
Tmp = BST;
if(!BST->Left)/*有右孩子或无子结点*/
{
BST = BST->Right;
}
else if(!BST->Right)
{
BST = BST->Left;
}
free(Tmp);
}
}
return BST;
}
什么是平衡二叉树(AVL)
搜索树结点不同插入次序将导致不同的深度和平均查找长度ASL,导致查找效率不同
比如:
很明显b图的平均查找次数最低,查找的效率也就最高而c图查找效率最低,因为b图更平衡
那么如何来衡量一棵树是否为平衡二叉树呢?
平衡因子(Balance Factor,BF):BF = hl - hr,hl和hr分别为T的左右子树的高度
平衡二叉树(Balanced Binary Tree)(AVL树)空树,或者任一结点左右子树高度差的绝对值不超过1,即|BF(T)| <= 1
平衡二叉树的高度能达到log2n吗?
设n高度为h的平衡二叉树的结点数,
h = 1时,结点最少是2个,可以两子结点
h = 2时结点最少是4,拿掉任何一个都不平衡且可能不是2层
Nh = Nh-1 + Nh-2 + 1很像
平衡二叉树的调整
按照平衡二叉树插入月份
RR旋转
根据平衡因子来度量是否平衡,如果插入Mar,May,Nov就不平衡了
不平衡的发现者是Mar,麻烦结点Nov在发现者右子树的右边,就叫做RR插入,需要RR旋转(右单旋)
如下图:
首先原本是平衡的,但是RR插入插入了一个结点使得树不平衡,因此需要RR旋转
A是发现者,BR下方插入的结点为破坏者,因此需要RR旋转,将B拎上来。
由于A右边的一定是大于A的,BL一定是小于B的因此将BL插到A的右边
这棵树原本是平衡的,插入15后变得不平衡,且是插在右子树的右子树上因此为RR插入,需要RR旋转,5为发现者,15为破坏者,就把10拎上去形成平衡二叉树
13也是破坏者也是插在右子树的右子树上,也是将10拎上去
LL旋转
现在在平衡状态,我们要插入四月Apr
现在平衡再次被破坏,此时被破坏的是Mar,而这次是插入在左子树的左边上因此需要LL旋转
发现者是Mar,麻烦结点Apr在发现者左子树的左边因而叫LL插入,需要LL旋转
如下图:开始时这棵树是平衡的,后来插入了一个结点在左子树的左边经过左左旋转变平衡
由于A的左子树一定是小于A的,B的右儿子也一定是大于B的因此将BR插入到A的右边
LR旋转
开始时树是平衡的
插入Jan后破坏了平衡且是May的左子树的右子树上
这种插入叫做LR插入因此需要LR旋转重点关注这三个结点,其中Mar大于Aug小于May因此将Mar拎上去将Aug作为其左儿子,May作为其右儿子
发现者是May,麻烦结点是Jan在左子树的右边,所以交LR插入,需要LR旋转
如下图:
通过LR插入使得树不平衡,因此我们需要LR旋转,将ABC调成CBA
RL旋转
开始时树是平衡的当Feb插入后就不平衡了,且是在右子树的左子树上
因此我们需要RL旋转,将
如下图
需要将ABC调整为CBA