上一节 已经说了 AVL树的插入 操作,可是 只有 插入,没有删除,怎么能叫 动态 查找表呢。
呵呵,博主 赶紧 去 研究了一番。下面 是成果:
AVL树的删除 大致 分为 两大块: 1. 查找节点 并 删除 2. 保持 删除 后 平衡因子的 影响
1. 首先 找到 这个 节点,如果 节点 不存在,直接 退出 函数
if (*tree == NULL){//没找到
return false;
}
2.如果 存在,分为 四种情况:(根 二叉 排序树的 删除 类似)
1.节点 为 叶子 节点,直接 删除
2.节点 的 左子树为空,则 用 节点的 右子树 代替 节点,并删除 这个节点。
3.节点的 右子树为空,则用 节点的 左子树 代替节点,并 删除 这个 节点
4.左右子树 都不为空 (下面 这样做,是为了 减少 旋转的 次数 ,如果 不懂,请 往下看,看完,再回头看)
4.1 如果 节点的 左子树的高度 >= 右子树的高度(LH,EH),则 从 左子树里 寻找 值 最大节点,将最大值赋值 给 节点,并删除 最大节点。
4.2如果节点 的 左子树的高度 《 右子树的高度(RH),则从 右子树里 寻找 值 最小的节点,并将 最小值 赋值给 节点,并删除最小节点
if (data == key){
if (p->lChild == NULL){//叶子节点 或者 左孩子为空
*tree = p->rChild;//
free(p);
*shorter = true;
}
else if(p->rChild == NULL){//右子树为空
*tree = p->lChild;
free(p);
*shorter = true;
}
else{//左右子树 都不为空
if (p->bf == LH || p->bf == EH){//左高,或者等高,//从左子树里寻找 最大节点(左子树的 最右下角)
AvlTree lc = p->lChild;
while (lc->rChild != NULL){
lc = lc->rChild;
}
p->data = lc->data;//替换以后 ,然后删除 替换节点
//然后从 左子树里 寻找删除节点,并删除它.
deleteAvlTree(&p->lChild,p->data,shorter);
}
else{//右高,从 右子树里寻找 最小的 替换
AvlTree rc = p->rChild;
while (rc->lChild != NULL){
rc = rc->lChild;
}
p->data = rc->data;
//然后从 右子树里寻找删除节点,并删除它
deleteAvlTree(&p->rChild,p->data,shorter);
}
}
3.至此, 删除 操作 已 完成了,可是删除后,必定 会 造成 树的 不平衡,怎么 去除 这些影响呢。
我们通过 上节 说的 旋转 来消除影响。
分为 两种情况:删除的 是 节点的 左子树 和 删除节点的 右子树。
删除的 是 节点的 左子树:分为三种情况。
1.如果 节点 的 平衡因子 为 1 (LH),删除后 ,平衡因子 变 为0, 变 矮了。
2.如果 节点的 平衡因子 为0(EH),删除后 节点 的 平衡 因子 变为 -1(RH), 没有 变矮
3.如果 节点的 平衡因子 为-1(RH),删除 左子树之后,节点的 平衡因子 变 为(-2),右边的 部分 失去 平衡了,我们 需要 对节点 进行 右平衡。(删除的 右平衡 和 插入的 右平衡 稍微 有点 不同,在 最后 会说到)。右平衡 会 根据 节点 右子树的 平衡因子 来 判断 是否变矮了
if (*shorter == true){//
switch (p->bf){
case LH:{//删除前 左子树高,删除左子树后,边矮了
p->bf = EH;
*shorter = true;
//*shorter = false;写错了
break;
}
case EH:{//之前 等高,删除后,右高
p->bf = RH;
*shorter = false;
break;
}
case RH:{//之前右高,删除左子树之后,右边失去平衡
//和前面的步骤不能反..
if (p->rChild->bf == EH){//自己画图,想一想
*shorter = false;
}
else{//左孩子之前 不等高,必会变矮
*shorter = true;
}
rightBalance(tree);
break;
}
}
同样 删除的 是 节点 的 右子树,也有三种情况:
1.节点 的平衡因子为0,(EH),节点的平衡因子 变为 1(LH), 没有变矮。
2.节点的平衡因子为-1(RH),节点的 平衡因子 边为0(EH),变矮了。
3.节点的 平衡因子为1(LH),删除右子树后,左边 失衡了,需要 对其 进行 左平衡化。(同样 删除的 左平衡 和 删除的 做平衡 略微 有些区别)。根据 节点的 左子树的平衡因子 来 判断 是否 变矮了。(可以 画图 来 看)。
if (*shorter == true){//删除右子树后,边矮了
switch (p->bf)
{
case LH:{//左边 失去平衡
// if (p->rChild->bf == EH){//画图考虑 考虑
// 这一块 还是不太明白
//顺序可以颠倒.
if(p->lChild->bf == EH){
*shorter = false;
}
else{
*shorter = true;
}
leftBalance(tree);
break;
}
case EH:{//之前 等高,删除右子树,左面边高了,整体没有变矮
p->bf = LH;
*shorter = false;
break;
}
case RH:{//之前右高,现在等高,边矮了
p->bf = EH;
*shorter = true;
//*shorter = false;写错了
break;
}
}
至此 删除 代码 已经全部 说完, 删除 函数 完整代码 如下:
/avl树删除
//返回 :删除成功 返回 true,没找到 返回 false
//shorter : 是否变短了
bool deleteAvlTree(AvlTree * tree,TreeType key,bool * shorter){
if (*tree == NULL){//没找到
return false;
}
else{
AvlTree p = *tree;
TreeType data = p->data;
if (data == key){
if (p->lChild == NULL){//叶子节点 或者 左孩子为空
*tree = p->rChild;
free(p);
*shorter = true;
}
else if(p->rChild == NULL){//右子树为空
*tree = p->lChild;
free(p);
*shorter = true;
}
else{//左右子树 都不为空
if (p->bf == LH || p->bf == EH){//左高,或者等高,//从左子树里寻找 最大节点(左子树的 最右下角)
AvlTree lc = p->lChild;
while (lc->rChild != NULL){
lc = lc->rChild;
}
p->data = lc->data;//替换以后 ,然后删除 替换节点
//然后从 左子树里 寻找删除节点,并删除它.
deleteAvlTree(&p->lChild,p->data,shorter);
}
else{//右高,从 右子树里寻找 最小的 替换
AvlTree rc = p->rChild;
while (rc->lChild != NULL){
rc = rc->lChild;
}
p->data = rc->data;
//然后从 右子树里寻找删除节点,并删除它
deleteAvlTree(&p->rChild,p->data,shorter);
}
}
return true;
}
else if(data > key){
if (deleteAvlTree(&p->lChild,key,shorter) == false){//没找到
return false;
}
if (*shorter == true){//
switch (p->bf){
case LH:{//删除前 左子树高,删除左子树后,边矮了
p->bf = EH;
*shorter = true;
//*shorter = false;写错了
break;
}
case EH:{//之前 等高,删除后,右高
p->bf = RH;
*shorter = false;
break;
}
case RH:{//之前右高,删除左子树之后,右边失去平衡
//和前面的步骤不能反..
if (p->rChild->bf == EH){//自己画图,想一想
*shorter = false;
}
else{//左孩子之前 不等高,必会变矮
*shorter = true;
}
rightBalance(tree);
break;
}
}
}
return true;//删除成功
}
else{
if (deleteAvlTree(&p->rChild,key,shorter) == false){//没找到
return false;
}
if (*shorter == true){//删除右子树后,边矮了
switch (p->bf)
{
case LH:{//左边 失去平衡
// if (p->rChild->bf == EH){//画图考虑 考虑
// 这一块 还是不太明白
//顺序可以颠倒.
if(p->lChild->bf == EH){
*shorter = false;
}
else{
*shorter = true;
}
leftBalance(tree);
break;
}
case EH:{//之前 等高,删除右子树,左面边高了,整体没有变矮
p->bf = LH;
*shorter = false;
break;
}
case RH:{//之前右高,现在等高,边矮了
p->bf = EH;
*shorter = true;
//*shorter = false;写错了
break;
}
}
}
return true;
}
}
}
最后 得 说一说 ,插入的 左(右)平衡 代码 和 删除的 左(右)平衡代码的 区别。
其实 就多了 一种 节点 左子树 平衡因子的 情况,插入 没有 Case EH 的情况,删除 有 Case EH的情况。
//左平衡
void leftBalance(AvlTree * tree){
AvlTree p = *tree;
AvlTree lc = p->lChild;
switch (lc->bf){//
case LH:{//LL型,插入在左子树的左子树上
p->bf = lc->bf = EH;
R_Rotate(tree);
//R_Rotate(tree);尽量按上面来写
//p->bf = lc->bf = EH;
break;
}
case RH:{//LR型,插入在左子树的右子树上
AvlTree rc = lc->rChild;
switch (rc->bf){//设置 tree ,lc的平衡因子
case LH:{//插入在 rc 的 左子树上
p->bf = RH;
lc->bf = EH;
break;
}
case EH:{//这个真没想明白。。。可能是 EH吗
p->bf = lc->bf = EH;
break;
}
case RH:{//插入在rc的右子树上
p->bf = EH;
lc->bf = LH;
break;
}
}
rc->bf = EH;//左子树的右子树 的平衡因子 必为 “等高”
L_Rotate(&(*tree)->lChild);//先左旋转 左子树
R_Rotate(tree);//在右旋转 根节点
break;
}
case EH:{//insertAVL用不着,deleteAVL得用
p->bf = LH;
lc->bf = RH;
R_Rotate(tree);
break;
}
}
}
//右平衡
void rightBalance(AvlTree * tree){
AvlTree p = *tree;
AvlTree rc = p->rChild;
switch (rc->bf)
{
case LH:{//插入在右子树的 左子树上
AvlTree lc = rc->lChild;
switch (lc->bf){//设置 p,lc的平衡度
case LH:{//插在 lc的左子树上
p->bf = EH;
rc->bf = RH;
break;
}
case EH:{
p->bf = rc->bf = EH;
break;
}
case RH:{//插在lc的右子树上.
p->bf = LH;
rc->bf = EH;
break;
}
}
lc->bf = EH;//右子树的左子树 最终平衡
R_Rotate(&(*tree)->rChild);//先平衡右子树
L_Rotate(tree);//再平衡根节点
break;//就因为少了一个break...调试了半天。。
}
case RH:{//插入在 右子树的 右子树上,左旋转
p->bf = rc->bf = EH;
L_Rotate(tree);
break;
//L_Rotate(tree);
//p->bf = rc->bf = EH;尽量按上面的来写
}
case EH:{//同样 insertVAL用不着,deleteVAL得用
p->bf = RH;
rc->bf = LH;
L_Rotate(tree);
break;
}
}
}
至于 case EH 的 具体 细节 怎么来的,请 画图。
觉得 AVL 树的 插入 和 删除 最难的 地方 就是 如果 计算 平衡因子。 这些 需要 通过 画图 来得知。
参考 网址:http://blog.csdn.net/sysu_arui/article/details/7897017
完整 代码 包括 测试 用例 的 工程 文件 网盘地址:http://pan.baidu.com/s/1hqGhXpq