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

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

时间: 2024-10-05 04:55:11

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

看数据结构写代码(32) 赫夫曼树编码以及译码

杂谈:最近有点慵懒,不好不好.好几天都没写代码,原本准备上星期完结 树 这一章节的.现在 又耽误了.哎.要抓紧时间啊. 下面直接上代码: 可以到我的网盘下载源代码,或者 直接拷贝下面的源代码 运行 网盘地址:点击打开链接 // HuffmanTree.cpp : 定义控制台应用程序的入口点. //哈弗曼编码,译码 #include "stdafx.h" #include <stdlib.h> #include <cstring> enum E_State { E

看数据结构写代码(66) 败者树

计算机的 内存 是 有限的,无法 存入 庞大的数据.当 遇到 大数据需要排序时,我们 需要 将 这些 数据 分段 从 硬盘里 读到 内存中,排好序,再 写入到 硬盘中,这些段 叫做 归并段.最后将 这些 分段 合并 成 一个 最终  完整 有序的 数据. 这里 操作的 时间 =  内部 排序 时间 +  外存读写时间 + 内部归并所需时间. 其中 外存 读写时间 最耗时,外存读写时间 = 读写次数 * 读写数据的时间 ,读写 数据的时间 因 设备 性能 而 影响,我们 无法控制,我们 只能 控制

看数据结构写代码(59) 键树的双链表示法

杂谈; 打败自己的 往往不是敌人,而是自己.坚持不易,且行且珍惜. 键树又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种.典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计.它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高 键树的根节点不包含字符,除根节点外的非叶子节点都只包含一个字符,叶子节点存储具体信息.: 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串:

看数据结构写代码(54)次优查找树

查找顺序表时,若 每个元素的概率 都相等 用 二分查找 效率 最高.但是 如果 概率 不相等时,(SOST)静态最优查找表 效率 要高于 二分查找.静态最优查找表 是 使得 从 根 到 每个节点的路径 长度 和 权值 乘积 之和 最小. 书上说的 静态最优 查找树的创建 时间 复杂度 较高,所以 用 次优 查找树(NOST) 代替. 下面 上代码: // Nost.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <cstd

看数据结构写代码(34) 树与回溯法(二)排序树(8皇后问题)

套用回溯 公式程序: void backtrack (int t) { if (t > n) { // 到达叶子结点,将结果输出 output (x); } else { // 遍历结点t的所有子结点 for (int i = f(n,t); i <= g(n,t); i ++ ) { x[t] = h[i]; // 如果不满足剪枝条件,则继续遍历 if (constraint (t) && bound (t)) backtrack (t + 1); } } } 写出 8皇后问

看数据结构写代码(60 ) 键树的多重链表表示(Trie树)

trie树,是用 树的 多重链表来表示 树的.每个节点 有 d 个指针域.若从键树中的某个节点到叶子节点的路径上每个节点都只有一个孩子,则可以把 路径上的所有节点压缩成一个叶子节点,且在叶子节点中 存储 关键字 以及 根关键字相关的信息. 当节点的度 比较大时,选择 Trie树,要比 双链表树更为合适. tire树的 数据 压缩 是 挺与众不同的. 下面 给出 具体的 代码: 源代码工程文件网盘地址:http://pan.baidu.com/s/1cyTg6 // TrieTree.cpp :

看数据结构写代码(33) 树与回溯法(一) 子集树

回溯法 是 一种 在 穷举 中,裁剪 不满足 条件 的 分支,已达到 提高 效率的 方法.其基本原型 是 树的 先序遍历,从 树根 到 树叶的路径 是 问题的 一个 解. 回溯法的基本框架 =  确定 解空间 + 深度优先遍历 + 裁剪函数 + 确定结果函数 其中 解空间,分为 子集树 和 排序树. 具体 概念 详解:参考 点击打开链接  和 点击打开链接 递归算法通用 模板如下: 回溯法对解空间作深度优先搜索,因此,在一般情况下用递归方法实现回溯法. // 针对N叉树的递归回溯方法 void

看数据结构写代码(67) 置换 _ 选择排序(完结篇)

杂谈: 严蔚敏版<数据结构(C语言版)> 一书 终于看完了.这是 一个完结,也是 一个新的开端.<算法导论> 已到手. 置换选择排序的思想 是 将 归并段 尽量 变的 更大,而不是根据 内存 大小 限制在 固定的 大小. 这样 可以 利用赫夫曼树 来 进行 最优归并树,从而 使 外存 读写次数 最少. 下面给出 具体 代码:欢迎指出代码不足. // Replace_Selcetion.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h&q

看数据结构写代码(44) 判断无向图是否有环路

在 看 严蔚敏的 数据结构 一书 7.5小节时,书上 说" 判断有向图是否存在环要不无向图复杂.对于无向图来说,深度优先遍历过程中遇到回边(即指向已访问过的顶点的边),则必定存在环路". 看的不明白,所以 网上 百度了一下. 有了思路:故写下算法 和思路,以便以后 温故. 思路: 1.一个n个顶点,e条边的 无向图,若 e>= n,必有环路. 2.若 e < n ,需要 深度 遍历,并把 父节点传入 参数中,如果 遇到 一个 节点 被访问过 并且 不是 父节点,那么 就有环