参考了很多代码后自己的总结.个人感觉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);
}
}
我的程序都是在返回看那些一开看不懂的代码后,把它修改成我能看得懂的代码,仅供自己参考,大家可以在看完别人的代码后再来看我的,可能会明白一点