您的位置:首页 > 编程语言 > C语言/C++

AVL树(查找、插入、删除)——C语言

2019-08-13 23:11 2036 查看

AVL树

平衡二叉查找树(Self-balancing binary search tree)又被称为AVL树(AVL树是根据它的发明者G. M. Adelson-Velskii和E. M. Landis命名的),是在二叉查找树的基础上一个优化版本

AVL树的特点:

1.本身首先是一棵二叉查找树 2.带有平衡条件:每个结点的左右子树的高度之差的绝对值不超过1,也就是说,AVL树,本质上是带了平衡功能的二叉查找树 如果读者关于二叉查找树还不了解可以看一下这篇随笔:二叉查找树(查找、插入、删除)

 

AVL树的作用

AVL树解决了二叉查找树可能出现的极端情况,对于一般的二叉搜索树(Binary Search Tree),其期望高度(即为一棵平衡树时)为log2n,其各操作的时间复杂度(O(log2n))同时也由此而决定,但是在某些极端情况下

(如在插入的序列是有序的时),二叉搜索树将退化成近似链或链,此时,其操作的时间复杂度将退化成线性的,即O(n)。我们可以通过随机化建立二叉搜索树来尽量的避免这种情况,但是在进行了多次的操作之后,例如在在删除时,

我们总是选择将待删除节点的后继代替它本身,这样就会造成总是右边的节点数目减少,以至于树向左偏沉。这同时也会造成树的平衡性受到破坏,使得它的操作时间复杂度增加

例如下面这种情况:

/* 查找特定值 */
void SearchData(int targ, BSTree *nod)
{
if (nod != NULL)
{
if (nod->data == targ)
{
printf("查找值存在,值为%d\n", nod->data);
}
else if (nod->data > targ)
{
SearchData(targ, nod->left);    //递归查找左子树
}
else if (nod->data < targ)
{
SearchData(targ, nod->right);    //递归查找右子树
}
}
else if (nod == NULL)
{
printf("查找值不存在\n");
}
}
View Code  

2、插入节点(递归实现)

先梳理一下步骤

/* RR型旋转 */
AVLTree * RR_Rotation(AVLTree *nod)
{
AVLTree *temp;
temp = nod->right;    //临时保存nod的右子树

nod->right = nod->right->left;
temp->left = nod;

nod->height = GetNodeHeight(nod);    //更新节点高度
temp->height = GetNodeHeight(temp);

return temp;
}
View Code  

第三种:LR型

LR型失衡的操作相比于LL型失衡操作相对要复杂一点,需要旋转两次才能恢复平衡

第一步:对失衡节点的左子树进行RR型旋转

第二步:对失衡节点进行LL型旋转

因为之前已经写好了LL型和RR型的旋转,这里直接用就可以了,实现代码如下

/* LR型旋转 */
AVLTree * LR_Rotation(AVLTree *nod)
{
nod->left = RR_Rotation(nod->left);
nod = LL_Rotation(nod);

return nod;
}

LR型旋转图解:

/* RL型旋转 */
AVLTree * RL_Rotation(AVLTree *nod)
{
nod->right = LL_Rotation(nod->right);
nod = RR_Rotation(nod);

return nod;
}
View Code  

3、删除节点

删除节点比插入节点的操作还要稍微复杂一点,因为插入时,进行一次平衡处理(一次平衡处理可能包含多次旋转),整棵树都会处于平衡状态,而在删除时,需要进行多次平衡处理,才能保证树处于平衡状态

AVL树的删除操作前半部分和二叉查找树相同,只不过删除后要检查树是否失去平衡,如果失衡就需要重新调整平衡,并更新节点高度,总的来说可以分为如下几种情况

(1)删除叶子节点

情况一:删除节点后二叉树没有失去平衡

 

删除节点后树没有失去平衡,这种情况下只需要更新节点的高度

 

情况二:删除节点后二叉树失去平衡

上图的RE型失衡只有在删除操作时才可能出现(在插入时不可能出现),RE型失衡的旋转方式和RR型失衡的旋转方式一模一样

(虽然删除节点时遇到的失衡情况多了两种 LE和RE ,但是旋转的方式依旧是那四种(LL、RR、LR、RL))

实现代码:

/* 删除节点 */
AVLTree *DeletNode(AVLTree *nod, int DelData)
{
AVLTree *SNode = NULL; //后继节点
AVLTree *PSNode = NULL;    //后继节点的父节点
AVLTree *temp = NULL;    //临时保存待释放节点的子树,避免free后找不到左右子树

if (nod == NULL)
{
printf("删除节点不存在");
exit(0);
}
else if (DelData > nod->data)
{
nod->right = DeletNode(nod->right, DelData);

if (GetNodeHeight(nod->left) - GetNodeHeight(nod->right) > 1)
{
temp = nod->left;

if (GetNodeHeight(temp->left) >= GetNodeHeight(temp->right))    //LL型或LE型失衡、两种情况处理方式相同
{
nod = LL_Rotation(nod);
}
else    //LR型失衡
{
nod = LR_Rotation(nod);
}
}

nod->height = GetNodeHeight(nod);    //更新节点高度
}
else if (DelData < nod->data)
{
nod->left = DeletNode(nod->left, DelData);

if (GetNodeHeight(nod->right) - GetNodeHeight(nod->left) > 1)
{
temp = nod->right;

if (GetNodeHeight(temp->right) >= GetNodeHeight(temp->left))    //RR或RE型失衡、两种情况处理方式相同
{
nod = RR_Rotation(nod);
}
else    //RL型失衡
{
nod = RL_Rotation(nod);
}
}

nod->height = GetNodeHeight(nod);    //更新节点高度
}
else if (DelData == nod->data)
{
if (nod->right == NULL && nod->left == NULL)    //若待删除节点为叶子节点
{
free(nod);
return NULL;
}
}

 

(2)删除带有一个子节点的节点

else if (DelData == nod->data)
{
if (nod->right == NULL && nod->left == NULL)    //若待删除节点为叶子节点
{
free(nod);
return NULL;
}
else if (nod->right == NULL && nod->left != NULL)    //若待删除节点只有左子树
{
temp = nod->left;
free(nod);

return temp;
}
else if (nod->right != NULL && nod->left == NULL)    //若待删除节点只有右子树
{
temp = nod->right;
free(nod);

return temp;
}
}

 

(3)删除带有两个子节点的节点

删除带有两个子节点的节点时,需要找到待删除的节点的后继节点或者前驱节点(本篇随笔使用后继节点),具体方法在上一篇随笔已经列出二叉查找树(查找、插入、删除)——C语言,这里不再赘述

else    //若待删除节点既有左子树也有右子树
{
SNode = SearchSuccessorNode(nod->right);    //搜索后继节点
PSNode = SearchParentofSNode(nod->right, nod->right);    //搜索后继节点的父节点

if (nod->right == SNode)    //后继节点为待删除节点的右子树(后继节点有右子树和没有右子树的操作相同)
{
SNode->left = nod->left;
free(nod);

return SNode;
}
else if (nod->right != SNode && SNode->right == NULL)    //后继节点不为待删除节点的右子树,并且该后继节点没有右子树
{
SNode->left = nod->left;
SNode->right = nod->right;
PSNode->left = NULL;
free(nod);

return SNode;
}
else if (nod->right != SNode && SNode->right != NULL)    //后继节点不为待删除节点的右子树,并且该后继节点有右子树
{

PSNode->left = SNode->right;    //后继节点的右子树作为后继节点父节点的左子树
SNode->left = nod->left;
SNode->right = nod->right;
free(nod);

return SNode;
}
}
}

需要注意的是,删除节点时不会出现“后继节点不是删除节点的子节点,且后继节点有右子树”这种情况,如下图

上图的14节点已经失衡了,在插入的时候就会被调整,所以不会出现“后继节点不是删除节点的子节点,且后继节点有右子树”

(由于笔者能力有限,AVL树的删除操作分析的不是很清楚,若有疏漏,清指出)

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐