AVL树的插入删除查找算法实现和分析-1
至于什么是AVL树和AVL树的一些概念问题在这里就不多说了,下面是我写的代码,里面的注释非常详细地说明了实现的思想和方法。
因为在操作时真正需要的是子树高度的差,所以这里采用-1,0,1来表示左子树和右子树的高度差,而没有使用记录树的高度的方法。
代码如下:
typedef struct AVLNode
{
DataType cData;
int nBf; //结点的平衡因子,-1表示右子树的深度比左子树高1
//0表示左子树与右子树深度相等
//1表示左子树的深度比右子树高1
struct AVLNode *LChild;
struct AVLNode *RChild;
}AVLNode,*AVLTree;
typedef int BOOL;
enum enumHight{LH = 1, EH = 0, RH = -1} eHight;
enum enumBool{FALSE = 0, TRUE = 1} eBool;
void R_Rotate(AVLTree *pAT)
{
//对以*pAT为根的二叉树作右旋转处理,处理之后pAT指向新的树根结点
//即旋转处理之前的左子树的根结点
AVLTree lc = (*pAT)->LChild;
(*pAT)->LChild = lc->RChild;
lc->RChild = *pAT;
*pAT = lc;
}
void L_Rotate(AVLTree *pAT)
{
//对以*pAT为根的二叉树作左旋转处理,处理之后pAT指向新的树根结点
//即旋转处理之前的右子树的根结点
AVLTree rc = (*pAT)->RChild;
(*pAT)->RChild = rc->LChild;
rc->LChild = *pAT;
*pAT = rc;
}
void LeftBalance(AVLTree *pAT)
{
//对以指针pAT所指结点为根的二叉树作左平衡旋转处理,
//本算法结束时指针pAT指向新的结点
AVLTree lc = (*pAT)->LChild; //lc指向*pAT的左子树根结点
AVLTree rd = NULL;
if(lc)
switch(lc->nBf) //检查*pAT的左子树的平衡度,并作相应平衡处理
{
case LH: //新结点插入在*pAT的左孩子的左子树上,要作单右旋转处理
(*pAT)->nBf = lc->nBf = EH;
R_Rotate(pAT);
break;
case RH: //新结点插入在*pAT的左孩子的右子树上,要作双旋转处理
rd = lc->RChild;
switch(rd->nBf) //修改*pAT及其左孩子的平衡因子
{
case LH:
(*pAT)->nBf = RH;
lc->nBf = EH;
break;
case EH:
(*pAT)->nBf = lc->nBf = EH;
break;
case RH:
(*pAT)->nBf = EH;
lc->nBf = LH;
break;
default:;
}
rd->nBf = EH;
L_Rotate(&(*pAT)->LChild); //对*pAT的左子树作左平衡处理
R_Rotate(pAT); //对*pAT作右平衡处理
break;
default:;
}
}
void RightBalance(AVLTree *pAT)
{
//对以指针pAT所指结点为根的二叉树作右平衡旋转处理,
//本算法结束时指针pAT指向新的结点
AVLTree rc = (*pAT)->RChild;
AVLTree rd = NULL;
if(rc)
switch(rc->nBf)
{
case RH:
(*pAT)->nBf = rc->nBf = EH;
L_Rotate(pAT);
break;
case LH:
rd = rc->LChild;
switch(rd->nBf)
{
case LH:
(*pAT)->nBf = EH;
rc->nBf = RH;
break;
case EH:
(*pAT)->nBf = rc->nBf = EH;
break;
case RH:
(*pAT)->nBf = LH;
rc->nBf = EH;
break;
}
rd->nBf = EH;
R_Rotate(&(*pAT)->RChild);
L_Rotate(pAT);
default:;
}
}
BOOL InsertATNode(AVLTree *pAT, DataType c)
{
//若在平衡的二叉树pAT中不存在和c相同的关键字结点,
//则插入一个以c为数据元素的新结点,并返回1,否则返回0
//若因插入而使二叉排序树失去平衡,则作平衡旋转处理
static int taller = FALSE; //反映pAT树是否长高
if(!(*pAT))
{
//插入新结点,树长高,taller为TRUE;
(*pAT) = (AVLTree)malloc(sizeof(AVLNode));
(*pAT)->cData = c;
(*pAT)->LChild = (*pAT)->RChild = NULL;
(*pAT)->nBf = EH;
taller = TRUE;
}
else
{
if((*pAT)->cData == c)
{
//树中已存在和e相同关键字的结点,不插入
taller = FALSE;
return 0;
}
if(c < (*pAT)->cData)
{
//应该在*pAT的左子树中进行搜索
if(!InsertATNode(&(*pAT)->LChild, c)) //未插入
return 0;
if(taller) //已插入到树pAT并且左子树长高
{
switch((*pAT)->nBf) //检查*pAT的平衡因子
{
case LH: //原本左子树比右子树高,作左平衡处理
LeftBalance(pAT);
taller = FALSE;
break;
case EH: //原本左右子树等高,现左子树比右子树高1
(*pAT)->nBf = LH;
taller = TRUE;
break;
case RH: //原本右子树比左子树高,现左右子树等高
(*pAT)->nBf = EH;
taller = FALSE;
break;
}
}
}
else
{
//应该在*pAT的右子树中进行搜索
if(!InsertATNode(&(*pAT)->RChild, c)) //未插入
return 0;
if(taller) //已插入到树pAT并且右子树长高
{
switch((*pAT)->nBf) //检查*pAT的平衡因子
{
case LH: //原本左子树比右子树高,现左右子树等高
(*pAT)->nBf = EH;
taller = FALSE;
break;
case EH: //原本左右子树等高,现右子树比左子树高1
(*pAT)->nBf = RH;
taller = TRUE;
break;
case RH: //原本右子树比左子树高,作右平衡处理
RightBalance(pAT);
taller = FALSE;
break;
}
}
}
}
return 1;
}
BOOL DeleteNode(AVLTree *pAT, DataType c)
{
//若在平衡的二叉树pAT中存在和c相同的关键字结点,
//则删除一个以c为数据元素的结点,并返回1,否则返回0
//若因删除而使二叉排序树失去平衡,则作平衡旋转处理
static int lower = FALSE; //反映pAT树是否变矮
if(!(*pAT)) //树为空或结点不存在返回0
return 0;
if(c == (*pAT)->cData)
{
//存在要删除的结点
//查找用作替换的结点
AVLTree Min = FindMin((*pAT)->RChild);
if(Min != NULL) //存在右子树
{
//找到可以用作替换的点
(*pAT)->cData = Min->cData;
if(Min != (*pAT)->RChild)
{
AVLTree Parent = GetParent((*pAT)->RChild, Min->cData);
Parent->LChild = Min->RChild;
}
else
{
(*pAT)->RChild = Min->RChild;
}
free(Min);
}
else //不存在右子树
{
//找不到删除的结点
Min = *pAT;
*pAT = (*pAT)->LChild;
free(Min);
}
lower = TRUE;
}
else if(c < (*pAT)->cData)
{
//应该在*pAT的左子树中进行搜索
if(!DeleteNode(&(*pAT)->LChild, c)) //未删除
return 0;
if(lower) //树变矮
{
switch((*pAT)->nBf)
{
case LH: //原本左子树比右子树高,现在等高
(*pAT)->nBf = EH;
lower = TRUE;
break;
case EH: //原本左右子树等高,现右子树比左子树高1
(*pAT)->nBf = RH;
lower = FALSE;
break;
case RH: //原本右子树比左子树高,则作右平衡处理
RightBalance(pAT);
lower = TRUE;
break;
}
}
}
else if(c > (*pAT)->cData)
{
//应该在*pAT的右子树中进行搜索
if(!DeleteNode(&(*pAT)->RChild, c))
return 0;
if(lower)
{
switch((*pAT)->nBf)
{
case LH: //原本左子树比右子树高,则作左平衡处理
LeftBalance(pAT);
lower = TRUE;
break;
case EH: //原本左右子树等高,现左子树比右子树高1
(*pAT)->nBf = LH;
lower = FALSE;
break;
case RH: //原本右子树比左子树高,现左左子树等高
(*pAT)->nBf = EH;
lower = TRUE;
break;
}
}
}
return 1;
}
AVLTree FindMin(AVLTree AT)
{
//查找AT中最小的元素
while(AT && AT->LChild)
{
AT = AT->LChild;
}
return AT;
}
AVLTree GetParent(AVLTree AT, DataType c)
{
if(!AT || AT->cData == c)
return NULL;
if((AT->LChild && AT->LChild->cData == c) ||
(AT->RChild && AT->RChild->cData == c))
return AT;
else if((AT->LChild && c < AT->LChild->cData))
return GetParent(AT->LChild, c);
else
return GetParent(AT->RChild, c);
}
AVLTree FindATNode(AVLTree AT, DataType c)
{
if(!AT)
return NULL;
else if(AT->cData == c)
return AT;
else if(c < AT->cData)
{
return FindATNode(AT->LChild, c);
}
else
{
return FindATNode(AT->RChild, c);
}
}
现在对这种实现方法作一点小小的分析。
1、至于时间复杂度的分析就不必多说了,所有的算法的时间复杂度都为log2N。
2、从代码的数量可以看出这并不是一种好的实现方法,因为它的思路不太清晰和直观,操作实现比较复杂,还要用到二重指针,增加了程序出错的机会。
3、导致复杂的原因主要有两个,
1)第一个就是在高度信息储存方法上,不是采用每个结点保存自己作为一棵树的深度,而是保存着左右子树之差(左子树的深度 - 右子树的深度),从而产生了大量的判断和switch语句,让人眼花瞭乱,并且影响程序的效率和易读性,难以维护,所以用一个变量记录树的高度会让程序思路更清晰。
2)再者,在这里的旋转、插入的删除操作都要调节结点并改变指向结点的指针变量的值,在C语言中是没有引用的(C++有),所以就要用到双重指针,这无疑加大了程序的复杂度,而且使程序更容易出错。而避免这样的情况的做法是很简单的,只需要做一个简单的修改。就是让旋转插入等操作的返回值为指向结点的指针,而不是一个标志操作是否成功的状态值。而且这样做还有一个好处,就是通常最后插入的数据总是最先被访问,这样就可以根据返回的结点的指针马上访问该结点,而不用再在整棵树中查找。
算法如有错误,还请各位读者指出,谢谢!
改进后的算法会在下一篇的博客中给出。
AVL树的插入删除查找算法实现和分析-1的更多相关文章
- AVL 树的插入、删除、旋转归纳
参考链接: http://blog.csdn.net/gabriel1026/article/details/6311339 1126号注:先前有一个概念搞混了: 节点的深度 Depth 是指从根 ...
- AVL树的插入和删除
一.AVL 树 在计算机科学中,AVL树是最早被发明的自平衡二叉查找树.在AVL树中,任一节点对应的两棵子树的最大高度差为 1,因此它也被称为高度平衡树.查找.插入和删除在平均和最坏情况下的时间复杂度 ...
- AVL树的插入操作(旋转)图解
=================================================================== AVL树的概念 在说AVL树的概念之前,我们需要清楚 ...
- AVL树的插入与删除
AVL 树要在插入和删除结点后保持平衡,旋转操作必不可少.关键是理解什么时候应该左旋.右旋和双旋.在Youtube上看到一位老师的视频对这个概念讲解得非常清楚,再结合算法书和网络的博文,记录如下. 1 ...
- 数据结构系列之2-3树的插入、查找、删除和遍历完整版代码实现(dart语言实现)
弄懂了二叉树以后,再来看2-3树.网上.书上看了一堆文章和讲解,大部分是概念,很少有代码实现,尤其是删除操作的代码实现.当然,因为2-3树的特性,插入和删除都是比较复杂的,因此经过思考,独创了删除时分 ...
- 数据结构系列之2-3-4树的插入、查找、删除和遍历完整版源代码实现与分析(dart语言实现)
本文属于原创,转载请注明来源. 在上一篇博文中,详细介绍了2-3树的操作(具体地址:https://www.cnblogs.com/outerspace/p/10861488.html),那么对于更多 ...
- DS-二叉排序树的插入、查找和删除
2019-12-02(菜鸡开始学习了...) Data Structure 之 二叉排序树 二叉排序树是给定一个节点后,接下来插入的数如果比它大就会放到它的右孩子那边,比它小就会放到它的左孩子那边. ...
- 红黑树和AVL树的实现与比较-----算法导论
一.问题描述 实现3种树中的两种:红黑树,AVL树,Treap树 二.算法原理 (1)红黑树 红黑树是一种二叉查找树,但在每个结点上增加一个存储位表示结点的颜色,可以是red或black.红黑树满足以 ...
- 二叉搜索树-php实现 插入删除查找等操作
二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值: 若它的 ...
随机推荐
- 省部级干部list
省部级干部表 省(市.自治区) 省(市)委书记 省(市)人大主任 省(市)长 省(市)政协主席 北京市 郭金龙 杜德印 王安顺 吉林 天津市 黄兴国 肖怀远 黄兴国 臧献甫 上海市 韩正 殷一璀(女) ...
- HDU 4720 Naive and Silly Muggles (外切圆心)
Naive and Silly Muggles Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Oth ...
- CoreAnimation —— CALayer
概述 如上篇博文讲述,UIView中封装了很多系统方法,可以满足我们的大部分需求.但是,其也有很多限制.那些方法产生的动画基本单元为UIView,是非常重量级的对象,而且也不支持三维布局,大部分是对视 ...
- vim下设置tab
前言:大多数情况下tab键的宽度设置为4个空格,这个可以根据自己 的代码风格进行替换,然而当你提交不同的语言的代码的时候python 和c的时候就有区别了.c的话一般tab键做缩进,而python提交 ...
- Android:ListViewAdapter
MainActivity: package com.wyl.listview; import java.util.ArrayList; import java.util.HashMap; import ...
- CentOS Linux使用crontab运行定时任务详解
参考博文: (总结)CentOS Linux使用crontab运行定时任务详解
- 我的Python成长之路---GitHub使用克隆GitHub(SSH key配置)
六.克隆GitHub仓库 1.创建仓库目录,目录位置没有要求,比如D:\learngit. 2.配置ssh(如果不配置会每次都输入用户名和密码) 使用TortoiseGit生成ssh-key:开始菜单 ...
- ubuntu 安装python,easy_install和pip
ubuntu12.04默认安装的python为 ms@ubuntums:~$ pythonPython 2.7.3 (default, Aug 1 2012, 05:16:07) 我需要用python ...
- Citrix XenApp6.5 另类发布文档
快捷方式.文档发布 第一种方式: 1. 创建快捷方式 2. 创建批处理文件(例如:lnk.bat),并输入以下内容: Start c:\users\public\desktop\adobe ...
- Tri_integral Summer Training 8 总结
比赛链接 题目 B C E F G I 这是孟加拉国的区域赛. 开场ss读懂了c发现是个水题,于是去敲,结果手贱wa了一炮,不过很快就修正了错误.B题过了不少,我去读,发现是个水题,意识让Moor敲. ...