AVL树构建代码及其基本操作

参考了很多代码后自己的总结.个人感觉AVL树的代码比huffman树的代码更难理解,有些地方刚开始看的时候觉得疑惑,为什么网络上的讲解能那么肯定就是那样,后来自己画了很多二叉树后发现,确实就是那样,所以就干脆把某些东西当规律记下来了。-_-发个无语的小表情
一.简单的AVL树自我理解
二叉平衡树呀,就是说每个节点的左子树和右子树的高度差最多为1,一个节点没有左右子树,高度为0,然后每有一层高度加1,空树的高度在这里设定为-1.
二.avl树节点结构体定义

typedef struct AVL_Node
{
    int data; //数据
    int height;//节点高度 
    struct AVL_Node *llink;
    struct AVL_Node *rlink;
}Node;
typedef Node* PNode;

三.主程序讲解

void main()
{
    PNode root = NULL;
    Insert(&root,3);
    Insert(&root,2);
    Insert(&root,1);
    Insert(&root,4);
    Insert(&root,5);
    Insert(&root,6);
    Insert(&root,7);
    Insert(&root,10);
    Insert(&root,9);
    Insert(&root,8);
    Inoreder(root);
    printf("\n");
    printf("查找节点7\n");
    search(root,7);
    printf("删除节点10\n");
    Delete(&root,10);
    search(root,9);
    Inoreder(root);
}

接着看一下两个辅助函数

int NodeHeight(PNode*  ptrTree)  //返回节点高度
{  
    return (*ptrTree)==NULL ? -1 :(*ptrTree)->height;  
} 

int Max(int a,int b)//返回最大值
{
    return a>b ? a : b;
}

AVL树的构建是通过插入节点实现的,并且在插入的过程中逐渐调整节点高度,最终生成二叉平衡树。为什么不用递归实现建立呢,那样的话就是我们事先画好一颗平衡二叉树,然后按顺序输入了,这就没有意义了。我们的目的就是让程序帮我们把输入的节点改造成平衡二叉树。
看一下Inoreder插入代码

int Insert(PNode *node,int x)
{
    if((*node)==NULL)//节点为空插入这里
    {
        (*node) = (PNode)malloc(sizeof(Node));
        (*node)->data = x;
        (*node)->llink = NULL;
        (*node)->rlink = NULL;
        (*node)->height = 0;
        return 1;
    }
    else if(x == (*node)->data)//原来存在该节点返回0,递归结束 { return 0; } else if(x<(*node)->data)//待插入节点的值小于当前的节点 { if(Insert(&(*node)->llink,x)==0)//找寻左子树,返回0则说明该节点原来就存在 { return 0; //如果此处不返回0 说明成功插入 } else if(NodeHeight(&(*node)->llink)-NodeHeight(&(*node)->rlink)==2)//能走到这一步,说明还向左走了一步,如果失衡,只能为2 { Rotate_L(node); //左边高,再判断是LL型旋转还是LR型旋转 } } else if(x>(*node)->data) { if(Insert(&(*node)->rlink,x)==0)//找寻右子树 { return 0; } else if(NodeHeight(&((*node)->llink))-NodeHeight(&((*node)->rlink))==-2)//能走到这一步,说明还向右走了一步,如果失衡,只能为-2 { Rotate_R(node); } } (*node)->height = Max(NodeHeight(&((*node)->llink)),NodeHeight(&((*node)->rlink))) +1; //更新节点高度 return 1;//返回1,保证递归继续向下走 } 先讲一下一些设定。一个父节点的值要比左孩子的值一定大,并且比右孩子的值小。并且不能插入两个值相同的节点,不信就自己画一下,要不然就当成规则记下来。 要插入的节点高度初始化为0. 这里插入也用了递归。用其中一步举例,为什么判断Insert(&(*node)->llink,x)==0,因为如果该判断成立,说明要查的值之前就存在,这样就不能插入了,if条件成立后就返回0,递归回到上一步,if条件又成立,还是返回0,就这样一直返回0。 但是当插入节点不存在的时候,最终程序会递归到if((*node)==NULL)这一步,成功插入新节点,返回1,就能跳过Insert(&(*node)->llink,x)==0这个if判断,接下来就能进入if(NodeHeight(&(*node)->llink)-NodeHeight(&(*node)->rlink)==2)这个判断,来判断一下新插入的节点是否会造成二叉树失衡,并且因为是递归,程序会一层一层向上判断。判断是否失衡后,就可以一个一个更新节点高度了。并且在最后返回1,为什么这里不返回0,因为只有在插入节点存在是才会返回0,返回0是为了在已经确定不能插入的情况下,不再判断是否失衡(都没插入新节点,肯定不失衡呀),已经更新节点高度(没有插入新节点,原来的节点高度不变)。这里最后返回1,是为了回溯(好像是这样说的)时保证之前的每个节点都判断一下是否此时高度已经失衡,以及更新节点高度。另外一个节点的高度就是等于左子树和右子树中较大的哪个高度+1,因为作为父节点,在其子树的上一层呀。 ***接下来看一下调整代码吧,如果插入新节点使原来的节点失衡了该怎么办呢。*** 首先判断一下是左子树高度大,还是右子树高度大。如果(NodeHeight(&(*node)->llink)-NodeHeight(&(*node)->rlink)==2),说明新节点插入了失衡节点的左子树导致节点失衡,但此时又分为两种情况,是插在了失衡节点的左子树的左子树上了,还是插在了失衡节点的左子树的右子树上了吗?在这里大家可以去看一下其他博客里的图解。 大概就是这两种情况 ![这里写图片描述](http://img.blog.csdn.net/20161023211026555) ![这里写图片描述](http://img.blog.csdn.net/20161023211047352) 根据这两张图,自己再写一下高度就能得出如果NodeHeight(&(*node)->llink)-NodeHeight(&(*node)->rlink)==2,就是插入节点在左子树,并且当PNode temp = (*node)->llink;NodeHeight(&(temp->llink))-NodeHeight(&(temp->rlink)) == -1时,节点是插入在失衡节点的左子树的右子树上。
看一下两证情况如何调整
void Rotate_L(PNode* node)
{

PNode temp = (*node)->llink;
    if(NodeHeight(&(temp->llink))-NodeHeight(&(temp->rlink)) == -1)//左子树的右子树高度更高,RL旋转
    {
        adjustRR(&((*node)->llink));//先右旋 adjustLL(node);//再左旋 } else//左边高 { adjustLL(node);//直接左旋 } }
void adjustRR(PNode* node)
{
    PNode temp = (*node)->rlink; //记录失衡节点的右孩子
    (*node)->rlink = temp->llink;//失衡节点的右孩子为temp的左孩子
    temp->llink = (*node);//temp的左孩子为失衡节点
    (*node)->height = Max(NodeHeight(&((*node)->llink)),NodeHeight(&((*node)- >rlink))) +1; //更新节点 失衡节点 (temp)->height = Max(NodeHeight(&(temp->llink)),NodeHeight(&(temp->rlink))) +1; //更新节点 失衡节点的右孩子   高度会发生变化的只有这两个节点
    (*node) = temp;
}
void adjustLL(PNode* node)
{
    PNode temp = (*node)->llink; //保存失衡节点的左节点
    (*node)->llink = temp->rlink;//失衡节点的左节点为temp的右节点
    temp->rlink = (*node);//temp的右节点为失衡节点
    (*node)->height = Max(NodeHeight(&((*node)->llink)),NodeHeight(&((*node)->rlink))) +1; //更新高度 失衡节点 (temp)->height = Max(NodeHeight(&(temp->llink)),NodeHeight(&(temp->rlink))) +1; //更新高度  失衡节点的左孩子
    (*node) = temp;
}

当插入到左子树的左子树上时,就把失衡节点的左孩子赋值为失衡节点的左孩子的右孩子,失衡节点的左孩子右孩子复制为失衡节点,并以其为根节点。
当插到右子树上时,原理相同。
复杂的情况当插入到左子树的右子树上时,先对左子树根节点就行rr操作,再对失衡节点进行ll操作,另一种情况原理相同.
查找函数

int search(PNode node,int x)//查询
{
        //PNode temp;
    while(node!=NULL)
    {
    // temp = node;
        if(node->data == x)
        {
            if(node->llink!=NULL && node->rlink!=NULL)
            {
                printf("节点数据:%d 高度:%d 左孩子:%d 右孩子%d 平衡因子%d \n",node->data,node->height,node->llink->data,node->rlink->data,NodeHeight(&(node->llink))-NodeHeight(&(node->rlink)));
            }
            if(node->llink==NULL && node->rlink!=NULL)
            {
                printf("节点数据:%d 高度:%d 左孩子为空 右孩子%d 平衡因子%d\n",node->data,node->height,node->rlink->data,NodeHeight(&(node->llink))-NodeHeight(&(node->rlink)));
            }
            if(node->llink!=NULL && node->rlink==NULL)
            {
                printf("节点数据:%d 高度:%d 左孩子%d 右孩子为空 平衡因子%d\n",node->data,node->height,node->llink->data,NodeHeight(&(node->llink))-NodeHeight(&(node->rlink)));
            }
            if(node->llink==NULL && node->rlink==NULL)
            {
                printf("节点数据:%d 高度:%d 左孩子为空 右孩子为空 平衡因子%d\n",node->data,node->height,NodeHeight(&(node->llink))-NodeHeight(&(node->rlink)));
            }
            return 1;   
        }
        else if(x>node->data)
        {
            node = node->rlink;
        }
        else if(x<node->data)
        {
            node = node->llink;
        }
    }
    return 0;
}

没什么好说的。
删除函数

void Delete(PNode *node,int x)//删除节点
{
if((*node)==NULL)//节点为空,不存在删除节点
    {
        return ;
    }
    else if(x>(*node)->data) { Delete(&(*node)->rlink,x);//左递归 } else if(x<(*node)->data) { Delete(&(*node)->llink,x);//右递归 } else if(x == (*node)->data) { if((*node)->llink == NULL)//没有左孩子 { PNode temp = (*node); (*node)=(*node)->rlink;
            free(temp);
        }
        else if((*node)->rlink == NULL)//没有右孩子 { PNode temp = (*node); (*node)=(*node)->llink;
            free(temp);
        }
        else
        {
            PNode temp = (*node)->llink;
            if(temp==NULL)
            {
                 temp = (*node);
                (*node)=(*node)->rlink;
                free(temp);
            }
            else
            {
            while(temp->rlink!=NULL)//左子树最大节点代替删除节点
            {
                temp = temp->rlink;
            }
            (*node)->data = temp->data;
            Delete(&(*node)->llink,temp->data);//递归删除用来代替的节点 } } } if(*node) { (*node)->height = Max(NodeHeight(&((*node)->llink)),NodeHeight(&((*node)->rlink))) +1; //更新节点高度 } } 

删除规则就是,删除节点是叶节点时直接删除,如果左右孩子只有一个的话,就用之代替,如果左右孩子都存在,用A的左子树最大数据或右子树最小数据(假设B节点)代替A节点的数据,并递归地删除B节点。
AVL的树的删除策略与二叉查找树的删除策略相似,只是删除节点后造成树失去平衡性,需要做平衡处理。
中序遍历函数
和遍历普通的二叉树没有区别。

void Inoreder(PNode tree)//中序遍历
{
    if(tree == NULL)
    {
        return ;
    }
    else
    {
    Inoreder(tree->llink);
    printf("%d ",tree->data);
    Inoreder(tree->rlink);
    }
}

我的程序都是在返回看那些一开看不懂的代码后,把它修改成我能看得懂的代码,仅供自己参考,大家可以在看完别人的代码后再来看我的,可能会明白一点

    原文作者:AVL树
    原文地址: https://blog.csdn.net/gilgameshg/article/details/52904708
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞