看数据结构写代码(57) AVL树的删除

北慕城南 提交于 2019-11-29 21:11:38

上一节 已经说了 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



易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!