AVL树介绍和各操作实现图文详解
AVL树介绍
AVL(Adelson-Velskii 和Landis )树是带有平衡条件的二叉查找树。该条件便是其每个节点的左子树和右子树的高度最多相差1(空树的高度定义为-1),其深度是O(log N)。
简而言之,一颗AVL树是其每个节点的左子树和右子树的高度最多相差1的二叉查找树。
如上图,左边的是AVL树而右边的不是AVL树,因为在树(2)中节点5左右子树的高度差超过了1。
AVL树的结构体如下:
#include<stdio.h>
#include<stdlib.h>
#define Maxsize 20
typedef struct AVLTREE
{
int Data;//存放数据
int Height;//高度
struct AVLTREE *Leftchild;//左子树
struct AVLTREE *Rightchild;//右子树
}*AVLTree;
节点的高度等于该节点左右孩子中的较大高度加一。
获取节点高度的代码:
//获取节点高度
int GetHeight(AVLTree root)
{
if(root==NULL)
return -1;
else
return MaxHeight(GetHeight(root->Leftchild),GetHeight(root->Rightchild))+1;
}
AVL树的操作集
旋转
在不断往AVL树中添加新节点的过程中往往会导致AVL树“失衡”(即有节点左右子树的高度差超过了1)的出现,
如上图,新插入的节点7便导致了树的失衡,尽管节点4和节点5同时失衡,但我们只需对距离新节点最近的失衡节点5进行研究和处理,在这里我们将新插入的导致树失衡的节点7叫做“麻烦节点”,将距离麻烦节点最近的失衡节点5叫做“发现者”,接下来我们便将对插入过程中四种形式的“发现者”和“麻烦节点”进行讨论。
RR插入(左单旋)
就拿上面的那个例子来讲,它便是一个RR插入的情况
RR插入指“麻烦节点”是“发现者”右孩的右孩,即新插入的节点在离其最近的“失衡”节点的右儿子的右儿子处。
此时我们就只对“发现者”和其到“麻烦节点”路径上的节点做操作,先单独把它们拎出来,然后对它们进行左单旋
怎么理解这个过程呢。。。打个比方吧,你就想象一下这三个节点是三个长在一根树枝上的大苹果,你将这根树枝剪下来,然后又在节点6上打个结绑回树上去,这样子节点5和节点7在重力的作用下自然下垂,成为了节点6的左右孩子,让我们看看把“苹果绑回去后的效果”
这样就成功让失去平衡的树重新恢复平衡了。
但这只是RR插入中较为简单的一种情况,试着想想,左单旋过程中“发现者”顶替了右孩的左儿子节点的位置,那如果“发现者”的右孩有左儿子怎么办?右孩的左儿子何去何从?
情况如上图,当发现者右孩的左儿子不为空时,我们旋转的同时只需要将右孩的左儿子变成发现者的右儿子就行了
总而言之,左单旋是个将发现者节点变成其右孩的左孩子节点,且让发现者的右孩代替发现者原本的位置的一个过程,在这个过程中,如果发现者右孩的左儿子不为空时,我们旋转的同时只需要将右孩的左儿子变成发现者的右儿子就行了。
PS:我们要注意左单旋过程中的顺序,在左单旋过程中,我们要先取得发现者的右孩,再将右孩的左儿子插入到发现者的右孩位置上,再将发现者插入到右孩的左儿子位置上,最后再返回右孩的位置让右孩代替原本发现者的位置。
RR插入(左单旋)的代码实现如下:
//RR插入(左单旋)
AVLTree Right_Right_rotation(AVLTree root)
{
AVLTree Right_Chlid;
Right_Chlid=root->Rightchild;//先取出“发现者”的右孩
root->Rightchild=Right_Chlid->Leftchild;//再让“发现者”的右儿子指向其原本右孩的左儿子
Right_Chlid->Leftchild=root;//让其成为原本右孩的左儿子
root->Height=MaxHeight(GetHeight(root->Leftchild),GetHeight(root->Rightchild))+1;//更新原“发现者”的高度
Right_Chlid->Height=MaxHeight(GetHeight(Right_Chlid->Leftchild),GetHeight(Right_Chlid->Rightchild))+1;//更新原“发现者”右孩的高度
return Right_Chlid;//返回右孩,使其代替原本发现者的位置
}
LL插入(右单旋)
LL插入(右单旋)是RR插入(左单旋)的一个镜像操作,只要将上面RR插入(左单旋)的一些操作反过来就行,主要是对“发现者”及其左孩的一个操作。例子如下图
如图所示,当麻烦节点是在发现者左孩的左孩的位置上时,就叫做LL插入,为使其恢复平衡,我们对其进行右单旋操作
当然,我们进行右单旋的过程中同时也要注意发现者左孩的右儿子是否为空,
若左孩的右儿子不为空则将其插入到发现者左孩的位置上。
PS:其实我们不用判断左孩的右儿子是否为空,不管它是否为空,直接将其插入到发现者左孩的位置上就行
总而言之,右单旋就是个把发现者变成其左孩的右儿子,其左孩的右儿子变成发现者左儿子,并返回发现者原本的左孩的地址使其代替发现者在树上原本的位置的一个过程。
LL插入(右单旋)的代码实现如下:
//LL插入(右单旋)
AVLTree Left_Left_rotation(AVLTree root)
{
AVLTree Left_Chlid;
Left_Chlid=root->Leftchild;//先取出“发现者”的左孩
root->Leftchild=Left_Chlid->Rightchild;//再让“发现者”的左儿子指向其原本左孩的右儿子
Left_Chlid->Rightchild=root;//让其成为原本左孩的右儿子
root->Height=MaxHeight(GetHeight(root->Leftchild),GetHeight(root->Rightchild))+1;//更新原“发现者”的高度
Left_Chlid->Height=MaxHeight(GetHeight(Left_Chlid->Leftchild),GetHeight(Left_Chlid->Rightchild))+1;//更新原“发现者”左孩的高度
return Left_Chlid;//返回左孩,使其代替原本发现者的位置
}
RL插入(右左双旋)
让我们再回到开头的那个例子,若是当时麻烦节点不是发现者右孩的右儿子,而是右孩的左儿子,那该怎么办
当麻烦节点是发现者节点右孩的左儿子时,该情况叫做RL插入,我们要对发现者进行右左双旋操作(其实右单旋是对发现者右孩的动作,左单旋才是对发现者的动作),该操作顺序如下先对发现者节点的右孩进行右单旋,这样子这棵树就变成了RR插入的情况
既然变成了RR插入的情况,那就再对发现者进行左单旋操作
通过以上两个步骤,我们成功地让RL插入情况先失衡的树重获平衡!
总而言之,右左双旋,就是先对发现者的右孩进行右单旋,再对其发现者进行左单旋的一个过程。
PS:由于左右单旋的代码我们前面都打完了,所以双旋操作的代码反而更简单。。
RL插入(右左双旋)的代码实现如下:
/*RL插入(右左双旋)*/
AVLTree Right_Left_rotation(AVLTree root)
{
root->Rightchild=Left_Left_rotation(root->Rightchild);//先对其右孩进行右单旋
return Right_Right_rotation(root);//再对发现者进行左单旋
}
LR插入(左右双旋)
最后,我们来讲讲LR插入,LR插入是指麻烦节点在发现者左孩的右儿子的位置上,如下图
针对于这种情况,其实要进行的操作与RL插入的操作大同小异,先对发现者的左孩进行左单旋,使其变成LL插入情况
最后对发现者进行右单旋
LR插入情况下的失衡就这样被我们通过左右双旋恢复平衡。
总而言之,左右双旋,就是先对发现者的左孩进行左单旋,再对发现者进行右单旋的操作过程。
LR插入(左右双旋)的代码实现如下:
/*LR插入(左右双旋)*/
AVLTree Left_Right_rotation(AVLTree root)
{
root->Leftchild=Right_Right_rotation(root->Leftchild);//先对左儿子进行左单旋
return Left_Left_rotation(root);//在对发现者进行右单旋
}
插入
AVL树插入一个节点过程和二叉搜索树的插入过程是差不多的,只是比二叉搜索树多了平衡条件而已,所以在写插入代码时我们可以适当模仿二叉搜索树的插入写法。
由于咱AVL树对树深度的严格控制(O(log N)),所以咱在这里可以大胆使用递归算法。
插入的代码实现如下:
/*插入数据*/
AVLTree Insert(AVLTree root,int x)
{
AVLTree Tree=root;//获取节点地址
if(root==NULL) //当其传入的是零指针时说明找到了合适的位置
{
Tree=(AVLTree)malloc(sizeof(struct AVLTREE));
Tree->Data=x;
Tree->Height=0;
Tree->Leftchild=Tree->Rightchild=NULL;
}
else //还未找到合适的位置时
{
if(x<Tree->Data)//当要插入的数据比当前节点的值要小,便将其插入到左子树
{
Tree->Leftchild=Insert(Tree->Leftchild,x);//将其插入到左子树中
if(GetHeight(Tree->Leftchild)-GetHeight(Tree->Rightchild)==2)//如果插入完成后此节点为“发现者”(左右子树相差刚好为2的节点)
{
if(x<Tree->Leftchild->Data)//要插入的数据比其左儿子小的话说明“麻烦节点”(新插入的导致平衡被破坏的节点) 在其左儿子的左边
Tree=Left_Left_rotation(Tree);//左左(LL)插入,对其进行右单旋
else
Tree=Left_Right_rotation(Tree); //左右(LR)插入,对此节点进行左右双旋
}
}
else if(x==Tree->Data)//当要插入的数据和当前节点的值一样时
printf("数值重复!!\n");
else if(x>Tree->Data)//当要插入的数据比当前节点的数据大时,将其插入到右子树中
{
Tree->Rightchild=Insert(Tree->Rightchild,x);//将其插入到右子树中
if(GetHeight(Tree->Rightchild)-GetHeight(Tree->Leftchild)==2)//若插入完成后该节点为“发现者”
{
if(x>Tree->Rightchild->Data) //要插入数据比“发现者”右儿子大则“麻烦节点”在其右儿子右边
Tree=Right_Right_rotation(Tree);//右右(RR)插入则对此节点进行左单旋
else
Tree=Right_Left_rotation(Tree);//右左插入则进行左右双旋
}
}
}
Tree->Height=MaxHeight(GetHeight(Tree->Leftchild),GetHeight(Tree->Rightchild))+1;//插入完成或平衡完毕后更新途径节点们的高度
return Tree;
}
为了帮助理解这段递归代码,诸位试着想象一下,把一整棵AVL树看成一个巨大的纸箱,这个大纸箱(根节点)里放着两个较小的纸箱(左右孩子),每个纸箱能且最多能装两个更小的纸箱,纸箱都有属于自己的值,你要拿你想放入的纸箱上的数值和你打开的纸箱上的数值进行比较,你的数值更小则放在纸箱的左侧,数值更大则放在纸箱的右侧,如果相应的位置上已经有了纸箱,你就打开该纸箱,继续比较,直到找到了适合你的纸箱的空位为止,然后你就将你的纸箱放到该位置上(插入)。当你放下你的纸箱后你又需要将前面打开的纸箱一个个从里到外关上,在关闭的过程中你还要检查一下箱子里的两个箱子中存放了最多箱子的箱子之间差距会不会大于1个,如果大于1则对箱子更多的那个箱子进行处理(旋转)。
删除
AVL树的删除操作与二叉搜索树的删除操作也差不多,拿要删除的值和节点的值进行比较,比节点的小就往左子树上删,比节点大就往右子树删,大小一样就删掉,若果直到节点变成NULL都还没完成删除的话,那就说明该目标不在树上。
要删的目标节点有4种形态(加上不在树上的其实有5种),没有儿子的叶节点,只有左儿子或只有右儿子的,又或是左右儿子都有的节点
像上面的这样只有1个或没有儿子的节点的删除非常简单,直接删除加返回那个“独生子女”就可以了,但如果我们要删除像下面这样左右儿子都有的节点就有点麻烦了
上图的节点7可谓是“交通要塞”,贸然删除整棵树都会失衡,而且很难再次恢复,所以我们不能用删除的方式来对它进行操作,看看它的子树里,有没有可以代替它的节点?嗯,答案是肯定的,6和8就不错,看,它们多合适
6和8分别是目标节点左子树的最大节点和右子树的最小节点
所以这两个都可以用来替代目标节点,我们这里随便选用右子树的最小值来代替目标节点。
所以我们要有一个获得最小节点的函数,
获得最小节点的函数如下:
*获取最小节点*/
AVLTree GetMin(AVLTree root)
{
if(root->Leftchild!=NULL)//若其左儿子不为NULL则一直往左边递归
return GetMin(root->Leftchild);
else
return root;
}
删除的代码实现如下:
/*删除节点*/
AVLTree Delete(AVLTree root,int x)
{
AVLTree MinTree;//存放右儿子的最小子树
if(!root) printf("未找到该数据!\n");
if(x<root->Data)//目标数据比当前节点小就往其左子树删除
root->Leftchild=Delete(root->Leftchild,x);
else if(x>root->Data)//目标数据比当前节点大就往其右子树删除
root->Rightchild=Delete(root->Rightchild,x);
else if(root->Leftchild&&root->Rightchild)//找到了要删除的节点 ,且其既有左儿子又有右儿子
{
MinTree=GetMin(root->Rightchild);//获得该节点右子树的的最小值
root->Data=MinTree->Data;//把该节点的数据替换为右子树的的最小值
root->Rightchild=Delete(root->Rightchild,root->Data);//再删除右子树的最小值
}
else//找到了要删除的节点 ,其左右儿子不完整
{
MinTree=root;//先存放一下该节点的位置
if(!root->Leftchild)//如果其左节点为NULL,则其用右儿子替代
root=root->Rightchild;
else if(!root->Rightchild)//如果其右儿子为空,则其用左儿子替代
root=root->Leftchild;
free(MinTree);//释放旧节点
}
return root;
}
遍历
遍历方式有四种,前中后和层序遍历,在这四种遍历方式中我们主要讲讲难一点层序遍历
什么是层序遍历?层次遍历就是逐层地,从左至右地遍历二叉树。
那我们如何让一层一层的二叉树按顺序排成一个串串呢?
没错,我们可以用队列!
步骤如下图
我们先创建个循环队列,现将根节点放入队列中,每出队一个节点,我们就将这个节点的非空左右儿子按顺序入队,如此一来,我们就实现了层次遍历!
层次遍历代码实现如下:
/*层次遍历AVL树*/
void LevelTree(AVLTree root)
{
AVLTree Queue[Maxsize],Tree;//一个存放左右儿子的队列
int rear=0,front=0;//初始化队列头和队列尾
if(root!=NULL)//若根节点不为NULL则先将根节点存入队列中
Queue[++rear]=root;
while(front!=rear)//当头尾相同时说明队列空了
{
front=(front+1)%Maxsize;//先让队列头指向要出队的节点
Tree=Queue[front];//取出要出队的节点
printf("%d ",Tree->Data);//输出其值
/*将其左右儿子按顺序放入队列*/
if(Tree->Leftchild!=NULL)//先放入左儿子
{
rear=(rear+1)%Maxsize;//队尾后移一位
Queue[rear]=Tree->Leftchild;//放入队列
}
if(Tree->Rightchild!=NULL)//后放入右儿子
{
rear=(rear+1)%Maxsize;//队尾后移一位
Queue[rear]=Tree->Rightchild;//放入队列
}
}
}
前中后序遍历代码如下:
/*前序遍历*/
void PreOrder(AVLTree root)
{
if(!root) return;//若为空树则退出
else
{
printf("%d ",root->Data);
PreOrder(root->Leftchild);
PreOrder(root->Rightchild);
}
}
/*中序遍历*/
void InOrder(AVLTree root)
{
if(!root) return;//若为空树则退出
else
{
InOrder(root->Leftchild);
printf("%d ",root->Data);
InOrder(root->Rightchild);
}
}
/*后序遍历*/
void PostOrder(AVLTree root)
{
if(!root) return;//若为空树则退出
else
{
PostOrder(root->Leftchild);
PostOrder(root->Rightchild);
printf("%d ",root->Data);
}
}
完整代码
#include<stdio.h>
#include<stdlib.h>
#define Maxsize 20
typedef struct AVLTREE
{
int Data;//存放数据
int Height;//高度
struct AVLTREE *Leftchild;//左子树
struct AVLTREE *Rightchild;//右子树
}*AVLTree;
/*菜单*/
void Menu(void);
/*层序遍历树*/
void LevelTree(AVLTree root);
/*插入数据*/
AVLTree Insert(AVLTree root,int x);
/*删除数据*/
AVLTree Delete(AVLTree root,int x);
/*寻找最小值*/
AVLTree GetMin(AVLTree root);
/*右单旋*/
AVLTree Left_Left_rotation(AVLTree root);
/*左单旋*/
AVLTree Right_Right_rotation(AVLTree root);
/*左右双旋*/
AVLTree Left_Right_rotation(AVLTree root);
/*右左双旋*/
AVLTree Right_Left_rotation(AVLTree root);
/*先序遍历*/
void PreOrder(AVLTree root);
/*中序遍历*/
void InOrder(AVLTree root);
/*后序遍历*/
void PostOrder(AVLTree root);
/*比较树的高度*/
int MaxHeight(int Left,int Right);
/*得到树的高度*/
int GetHeight(AVLTree root);
int main(void)
{
AVLTree root=NULL;
int tragger;
int data=0;
while(1)
{
Menu();
scanf("%d",&tragger);
switch(tragger)
{
case 1:
printf("请输入数据(输入-1结束):\n");
scanf("%d",&data);
while(data!=-1)
{
root=Insert(root,data);
printf("请输入数据(输入-1结束):\n");
scanf("%d",&data);
}
break;
case 2: printf("层序遍历:");
LevelTree(root);
break;
case 3: printf("前序遍历:");
PreOrder(root);
break;
case 4: printf("中序遍历:");
InOrder(root);
break;
case 5: printf("后序遍历:");
PostOrder(root);
break;
case 6: printf("请输入要删除的数据:\n");
scanf("%d",&data);
root=Delete(root,data);
break;
case 0:
return 0;
}
}
return 0;
}
void Menu(void)
{
printf("\n///");
printf("\n// 1.插入数据: //");
printf("\n// 2.层序遍历二叉树: //");
printf("\n// 3.前序遍历二叉树: //");
printf("\n// 4.中序遍历二叉树: //");
printf("\n// 5.后序遍历二叉树: //");
printf("\n// 6.删除数据: //");
printf("\n// 0.退出: //");
printf("\n/\n");
}
/*插入数据*/
AVLTree Insert(AVLTree root,int x)
{
AVLTree Tree=root;//获取节点地址
if(root==NULL) //当其传入的是零指针时说明找到了合适的位置
{
Tree=(AVLTree)malloc(sizeof(struct AVLTREE));
Tree->Data=x;
Tree->Height=0;
Tree->Leftchild=Tree->Rightchild=NULL;
}
else //还未找到合适的位置时
{
if(x<Tree->Data)//当要插入的数据比当前节点的值要小,便将其插入到左子树
{
Tree->Leftchild=Insert(Tree->Leftchild,x);//将其插入到左子树中
if(GetHeight(Tree->Leftchild)-GetHeight(Tree->Rightchild)==2)//如果插入完成后此节点为“发现者”(左右子树相差刚好为2的节点)
{
if(x<Tree->Leftchild->Data)//要插入的数据比其左儿子小的话说明“麻烦节点”(新插入的导致平衡被破坏的节点) 在其左儿子的左边
Tree=Left_Left_rotation(Tree);//左左(LL)插入,对其进行右单旋
else
Tree=Left_Right_rotation(Tree); //左右(LR)插入,对此节点进行左右双旋
}
}
else if(x==Tree->Data)//当要插入的数据和当前节点的值一样时
printf("数值重复!!\n");
else if(x>Tree->Data)//当要插入的数据比当前节点的数据大时,将其插入到右子树中
{
Tree->Rightchild=Insert(Tree->Rightchild,x);//将其插入到右子树中
if(GetHeight(Tree->Rightchild)-GetHeight(Tree->Leftchild)==2)//若插入完成后该节点为“发现者”
{
if(x>Tree->Rightchild->Data) //要插入数据比“发现者”右儿子大则“麻烦节点”在其右儿子右边
Tree=Right_Right_rotation(Tree);//右右(RR)插入则对此节点进行左单旋
else
Tree=Right_Left_rotation(Tree);//右左插入则进行左右双旋
}
}
}
Tree->Height=MaxHeight(GetHeight(Tree->Leftchild),GetHeight(Tree->Rightchild))+1;//插入完成或平衡完毕后更新途径节点们的高度
return Tree;
}
/*层次遍历AVL树*/
void LevelTree(AVLTree root)
{
AVLTree Queue[Maxsize],Tree;//一个存放左右儿子的队列
int rear=0,front=0;//初始化队列头和队列尾
if(root!=NULL)//若根节点不为NULL则先将根节点存入队列中
Queue[++rear]=root;
while(front!=rear)//当头尾相同时说明队列空了
{
front=(front+1)%Maxsize;//先让队列头指向要出队的节点
Tree=Queue[front];//取出要出队的节点
printf("%d ",Tree->Data);//输出其值
/*将其左右儿子按顺序放入队列*/
if(Tree->Leftchild!=NULL)//先放入左儿子
{
rear=(rear+1)%Maxsize;//队尾后移一位
Queue[rear]=Tree->Leftchild;//放入队列
}
if(Tree->Rightchild!=NULL)//后放入右儿子
{
rear=(rear+1)%Maxsize;//队尾后移一位
Queue[rear]=Tree->Rightchild;//放入队列
}
}
}
//比较左右两个儿子的高度
int MaxHeight(int Left,int Right)
{
if(Left>Right)
return Left;
else
return Right;
}
//获取节点高度
int GetHeight(AVLTree root)
{
if(root==NULL)
return -1;
else
return MaxHeight(GetHeight(root->Leftchild),GetHeight(root->Rightchild))+1;
}
//LL插入(右单旋)
AVLTree Left_Left_rotation(AVLTree root)
{
AVLTree Left_Chlid;
Left_Chlid=root->Leftchild;//先取出“发现者”的左孩
root->Leftchild=Left_Chlid->Rightchild;//再让“发现者”的左儿子指向其原本左孩的右儿子
Left_Chlid->Rightchild=root;//让其成为原本左儿子的右儿子
root->Height=MaxHeight(GetHeight(root->Leftchild),GetHeight(root->Rightchild))+1;//更新原“发现者”的高度
Left_Chlid->Height=MaxHeight(GetHeight(Left_Chlid->Leftchild),GetHeight(Left_Chlid->Rightchild))+1;//更新原“发现者”左孩的高度
return Left_Chlid;//返回左孩,使其代替原本发现者的位置
}
//RR插入(左单旋)
AVLTree Right_Right_rotation(AVLTree root)
{
AVLTree Right_Chlid;
Right_Chlid=root->Rightchild;//先取出“发现者”的右孩
root->Rightchild=Right_Chlid->Leftchild;//再让“发现者”的右儿子指向其原本右孩的左儿子
Right_Chlid->Leftchild=root;//让其成为原本右孩的左儿子
root->Height=MaxHeight(GetHeight(root->Leftchild),GetHeight(root->Rightchild))+1;//更新原“发现者”的高度
Right_Chlid->Height=MaxHeight(GetHeight(Right_Chlid->Leftchild),GetHeight(Right_Chlid->Rightchild))+1;//更新原“发现者”右孩的高度
return Right_Chlid;//返回右孩,使其代替原本发现者的位置
}
/*LR插入(左右双旋)*/
AVLTree Left_Right_rotation(AVLTree root)
{
root->Leftchild=Right_Right_rotation(root->Leftchild);//先对左孩进行左单旋
return Left_Left_rotation(root);//再对发现者进行右单旋
}
/*RL插入(右左双旋)*/
AVLTree Right_Left_rotation(AVLTree root)
{
root->Rightchild=Left_Left_rotation(root->Rightchild);//先对其右孩进行右单旋
return Right_Right_rotation(root);//再对发现者进行左单旋
}
/*前序遍历*/
void PreOrder(AVLTree root)
{
if(!root) return;//若为空树则退出
else
{
printf("%d ",root->Data);
PreOrder(root->Leftchild);
PreOrder(root->Rightchild);
}
}
/*中序遍历*/
void InOrder(AVLTree root)
{
if(!root) return;//若为空树则退出
else
{
InOrder(root->Leftchild);
printf("%d ",root->Data);
InOrder(root->Rightchild);
}
}
/*后序遍历*/
void PostOrder(AVLTree root)
{
if(!root) return;//若为空树则退出
else
{
PostOrder(root->Leftchild);
PostOrder(root->Rightchild);
printf("%d ",root->Data);
}
}
/*删除节点*/
AVLTree Delete(AVLTree root,int x)
{
AVLTree MinTree;//存放右儿子的最小子树
if(!root) printf("未找到该数据!\n");
if(x<root->Data)//目标数据比当前节点小就往其左子树删除
root->Leftchild=Delete(root->Leftchild,x);
else if(x>root->Data)//目标数据比当前节点大就往其右子树删除
root->Rightchild=Delete(root->Rightchild,x);
else if(root->Leftchild&&root->Rightchild)//找到了要删除的节点 ,且其既有左儿子又有右儿子
{
MinTree=GetMin(root->Rightchild);//获得该节点右子树的的最小值
root->Data=MinTree->Data;//把该节点的数据替换为右子树的的最小值
root->Rightchild=Delete(root->Rightchild,root->Data);//再删除右子树的最小值
}
else//找到了要删除的节点 ,其左右儿子不完整
{
MinTree=root;//先存放一下该节点的位置
if(!root->Leftchild)//如果其左节点为NULL,则其用右儿子替代
root=root->Rightchild;
else if(!root->Rightchild)//如果其右儿子为空,则其用左儿子替代
root=root->Leftchild;
free(MinTree);//释放旧节点
}
return root;
}
/*获取最小节点*/
AVLTree GetMin(AVLTree root)
{
if(root->Leftchild!=NULL)//若其左儿子不为NULL则一直往左边递归
return GetMin(root->Leftchild);
else
return root;
}
代码试验
接下来我们试验一下我们代码的可靠性
我先随便手撸了一棵树
然后我们按照“9 8 7 1 3 5 4 6 10 12 15 14 13 16 2 11” 的顺序插入到一棵树中
然后遍历一下它
遍历结果
层次遍历:7,3,12,1,5,9,14,2,4,6,8,10,13,15,11,16
前序遍历:7,3,1,2,5,4,6,12,9,8,10,11,14,13,15,16
中序遍历:1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16
后序遍历:2,1,4,6,5,3,8,11,10,9,13,16,15,14,12,7
yes!结果和我手撸的一样,通过!
接下来我们把牛的一批的节点12给删了,看看它的结果
yes!OK!
总体来说咱们的代码还是阔以的!!!
参考资料
《数据结构与算法分析C语言描述》[美] 马克·艾伦·维斯著 冯舜玺译 机械工业出版社
如果天空不死大哥的博客
碎碎念
我的天,咱这要不就一个多月不更新,一更就差点把我整个人更没了。
现在我极度疲惫,
懒得检查(゚▽゚*)
如有错误的地方,
还请赐教︿( ̄︶ ̄)︿
看电影去啦~~
撒花。