上一节 已经说了 AVL树的插入 操作,可是 只有 插入,没有删除,怎么能叫 动态 查找表呢。
呵呵,博主 赶紧 去 研究了一番。下面 是成果:
AVL树的删除 大致 分为 两大块: 1. 查找节点 并 删除 2. 保持 删除 后 平衡因子的 影响
1. 首先 找到 这个 节点,如果 节点 不存在,直接 退出 函数
if (*tree == NULL){//没找到 return false; }
2.如果 存在,分为 四种情况:(根 二叉 排序树的 删除 类似)
1.节点 为 叶子 节点,直接 删除
2.节点 的 左子树为空,则 用 节点的 右子树 代替 节点,并删除 这个节点。
3.节点的 右子树为空,则用 节点的 左子树 代替节点,并 删除 这个 节点
4.左右子树 都不为空 (下面 这样做,是为了 减少 旋转的 次数 ,如果 不懂,请 往下看,看完,再回头看)
4.1 如果 节点的 左子树的高度 >= 右子树的高度(LH,EH),则 从 左子树里 寻找 值 最大节点,将最大值赋值 给 节点,并删除 最大节点。
4.2如果节点 的 左子树的高度 《 右子树的高度(RH),则从 右子树里 寻找 值 最小的节点,并将 最小值 赋值给 节点,并删除最小节点
<span style="white-space:pre"> </span>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),右边的 部分 失去 平衡了,我们 需要 对节点 进行 右平衡。(删除的 右平衡 和 插入的 右平衡 稍微 有点 不同,在 最后 会说到)。右平衡 会 根据 节点 右子树的 平衡因子 来 判断 是否变矮了
<span style="white-space:pre"> </span>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),删除右子树后,左边 失衡了,需要 对其 进行 左平衡化。(同样 删除的 左平衡 和 删除的 做平衡 略微 有些区别)。根据 节点的 左子树的平衡因子 来 判断 是否 变矮了。(可以 画图 来 看)。
<span style="white-space:pre"> </span>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