什么是AVL搜索二叉树?
AVL树本质上还是一棵二叉搜索树,它的特点是:
1.本身首先是一棵二叉搜索树。
2.带有平衡条件:每个结点的左右子树的高度之差的绝对值(平衡因子)最多为1。
也就是说,AVL树,本质上是带了平衡功能的二叉查找树(二叉排序树,二叉搜索树)。
typedef struct AVLNode{
int key; //用来存储节点所保存的值
int height; //用来记录节点的高度
struct AVLNode *left; //左儿子节点
struct AVLNode *right; //右儿子节点
}AVLNode;
//定义两个宏:一个用MAX来求左右儿子高度的最大者
// 一个HEIGHT用来求树的高度
#define MAX(a,b) ((a)>(b)?(a):(b))
#define HEIGHT(p) ((p==NULL)?0:(((AVLNode *)(p))->height))
//定义树的类型
typedef struct AVLNode* AVLTree; //AVL树的定义
AVL搜索二叉树操作函数声明
// 判断这个AVL是否是一棵空树
bool avltree_is_empty(AVLTree tree);
// AVL树的节点个数
size_t avltree_size(AVLTree tree);
//AVL搜索二叉树的高度
size_t avltree_height(AVLTree tree);
// 查找数据 查找成功返回true
bool avltree_find(AVLTree tree,int key);
// 中序遍历(从小到大)
void avltree_mid_foreach(AVLTree tree,void(*func)(AVLNode*));
//层序遍历(需要用到队列)
void avltree_level_foreach(AVLTree tree,void(*func)(AVLNode*));
//二叉搜索树的插入一个节点
int avltree_insert(AVLTree *ptree,int key);
//二叉搜索树删除节点
int avltree_delete(AVLTree *ptree,int key);
//销毁这棵AVL搜索二叉树
void avltree_destroy(AVLTree tree);
接下来我们把这些函数一个一个的实现
bool avltree_is_empty(AVLTree tree){ //判断是否为空
return tree == NULL; //只需要判断根节点是否为NULL即可
}
我们选择用递归的方式来实现:节点个数=本身+左子树的节点个数+右子树的节点个数
size_t avltree_size(AVLTree tree){
if(!tree){
return 0;
}
return avltree_size(tree->left)+avltree_size(tree->right) + 1;
AVL树的高度即等于根节点的高度
size_t avltree_height(AVLTree tree){
if(!tree){
return 0;
}
return tree->height;
}
选择用递归的方法:如果根的值等于搜索的值,成功找到,如果搜索的元素小于根的值去左子树找,如果大于就去右子树找
bool avltree_find(AVLTree tree,int key){
if(!tree){ //没有找到返回false
return false;
}
if(key == tree->key){
return true;
}
if(key < tree->key){
return avltree_find(tree->left,key);
}
if(key > tree->key){
return avltree_find(tree->right,key);
}
}
先遍历左子树,再遍历根节点,最后遍历右子树,并通过func这个函数指针返回树的节点以供用户使用
void avltree_mid_foreach(AVLTree tree,void(*func)(AVLNode*)){
if(tree){
avltree_mid_foreach(tree->left,func);
func(tree);
avltree_mid_foreach(tree->right,func);
}
}
先遍历第一层,再一层一层的往下遍历
我们需要使用到队列这个数据结构,我先提供队列的函数,并不多做讲解:
#ifndef _QUEUE_H__
#define _QUEUE_H__
#include "AVLtree.h"
typedef struct Queue{
void **vect; //万能指针,可以存储任意类型的数据
size_t size; //队列大小
size_t cnt; //当前数据个数
size_t fir; //用来保存队首的位置 队尾的位置可以用(fir+cnt)%size来获得
}Queue;
void queue_init(Queue *que,size_t size){ //初始化队列
que->vect = malloc(sizeof(AVLNode*)*size);
que->size = size;
que->cnt = 0;
que->fir = 0;
}
bool queue_is_empty(Queue *que){ //判断队列是否为空
return que->cnt == 0;
}
void queue_push(Queue *que,void *data){ //入队
que->vect[(que->cnt +que->fir)%que->size] = data;
que->cnt++;
}
void *queue_pop(Queue *que){ //出队
void *data = que->vect[que->fir];
que->fir = (que->fir+1)%que->size;
que->cnt--;
return data;
}
void queue_destroy(Queue *que){ //销毁队列,回收内存
free(que->vect);
que->vect = NULL;
}
#endif //queue.h
层序遍历的实现
如果根节点不为空,就把根入队,用一个节点来接受出队的节点,再判断这个出队节点的左右子节点是否为空,如果不为空就一一入队,详细的可以看注释(注意:入队的是节点,不是节点的值,这是关键点,这样我们就可以通过这个出队的节点来访问到它的左右节点)
void avltree_level_foreach(AVLTree tree,void(*func)(AVLNode *)){
Queue que;
queue_init(&que,avltree_size(tree)); //对列初始化
if(tree){ //如果根不为空,就把根节点入队
queue_push(&que,tree);
}
while(!queue_is_empty(&que)){ //如果队列不为空就一直重复入队出队操作
tree = queue_pop(&que); //用tree来接收出队的节点
func(tree); //供用户使用
if(tree->left){ //如果这个出队的节点tree的左节点存在,
queue_push(&que,tree->left); //那就把左节点入队
}
if(tree->right){ //如果右节点存在,那就右节点入队
queue_push(&que,tree->right);
}
}
}
先递归的销毁左子树和右子树,再销毁根节点
void avltree_destroy(AVLTree tree){
if(!tree){
avltree_destroy(tree->left);
avltree_destroy(tree->right);
free(tree);
}
tree = NULL;
}
由于AVL搜索二叉树的条件是要求平衡,即左右子树高度差不超过1,所以我们在插入一个节点后,该树的平衡性可能会遭到破坏,此时为了同时保持平衡性质和搜索树的性质,我们需要对插入节点的局部树进行旋转。
在根节点的左子树的左节点下面插入一个节点:会导致失衡
代码实现
static AVLNode* ll_rotate(AVLTree tree){
AVLNode *node = tree->left; //记录根节点的左儿子,用于返回
tree->left = node->right; // 左儿子的右节点接到根节点的左节点
node->right = tree; //根节点接到左儿子的右节点处
tree->height = MAX(HEIGHT(tree->left),HEIGHT(tree->right))+1; //更新根节点的高度(根节点的左右子树的高度的较大者)加上自身的高度
node->height = MAX(HEIGHT(node->left),tree->height)+1; //更新高度,同上
return node;
}
(PS:有了上面的LL旋转 下面的三个旋转大家都可以参照LL旋转来理解这些代码的含义,意思都相同,就是方向不同而已,所以不多做累述)
在根节点的右子树的右节点后面插入一个节点会导致失衡
代码实现
static AVLNode* rr_rotate(AVLTree tree){
AVLNode *node = tree->right;
tree->right = node->left;
node->left = tree;
tree->height = MAX(HEIGHT(tree->left),HEIGHT(tree->right))+1;
node->height = MAX(HEIGHT(node->right),tree->height)+1;
return node;
}
在根节点的左子树的右节点的后面插入一个节点,导致失衡,我们发现其实只需要对根节点的左子树进行一次RR旋转,再对根进行一次LL旋转即可。
代码实现
static AVLNode* lr_rotate(AVLTree tree){
tree->left = rr_rotate(tree->left);
return ll_rotate(tree);
}
在根节点的右子树的左节点的后面插入一个节点,导致失衡,我们发现其实只需要对根节点的右子树进行一次LL旋转,再对根进行一次RR旋转即可。
代码实现
static AVLNode* rl_rotate(AVLTree tree){
tree->right = ll_rotate(tree->right);
return rr_rotate(tree);
}
static AVLNode *create_avlnode(int key){
AVLNode *node;
if((node = malloc(sizeof(AVLNode)))==NULL){
return NULL;
}
node->key = key;
node->height = 1;
node->right = NULL;
node->left = NULL;
return node;
}
插入节点的代码实现
我们选择用递归实现插入操作,可能比较难理解,大家请仔细看注释
int avltree_insert(AVLTree *ptree,int key){ //传入二级指针来保存树的地址(即节点的地址),形参是地址,我们可以对实参的值进行修改
if(!(*ptree)){ //如果当前树为NULL,那这就是我们需要插入的位置
*ptree = create_avlnode(key); //在*ptree这个位置插入节点
if(*ptree == NULL){ //如果动态内存申请失败,就返回-1
return -1;
}
return 0; //成功插入返回0
}
int ret = 0; //定义一个变量来记录递归调用的返回值并且返回到上一层函数中去,这样上一层的函数才知道递归调用的下一层是否插入成功
if(key < (*ptree)->key){ //如果我们插入的值小于根节点的值
ret = avltree_insert(&(*ptree)->left,key); //那我们就去根节点的左边插入该节点,递归调用插入函数
if(ret == 0){ //如果返回值告诉我们已经插入成功了
if(HEIGHT((*ptree)->left)-HEIGHT((*ptree)->right) == 2){ //我们需要判断该AVL树是否失衡
//如果失衡,因为我们是在左边进行插入操作,所以肯定是左子树的高度大于右子树的高度
int hl = HEIGHT((*ptree)->left->left); //记录左子树的左子树的高度
int hr = HEIGHT((*ptree)->left->right); //记录左子树的右子树的高度
//这样我们可以判断是需要进行哪种旋转
if(hl>hr){ //如果左边高,那就是LL旋转
*ptree = ll_rotate(*ptree);
}else{ //如果是右边高,那就是LR旋转
*ptree = lr_rotate(*ptree);
}
}
(*ptree)->height = MAX(HEIGHT((*ptree)->left),HEIGHT((*ptree)->right))+1; //更新该节点的高度
}
return ret; //将插入的结果返回递归调用的上一层函数中,并且上一层的函数会根据这个返回值来进行下一步的操作
}else if(key > (*ptree)->key){ //如果插入的值大于根节点的值
ret = avltree_insert(&(*ptree)->right,key); //记录返回值
if(ret == 0){ //插入成功
if(HEIGHT((*ptree)->right)-HEIGHT((*ptree)->left) == 2){ //失衡
int hl = HEIGHT((*ptree)->right->left);
int hr = HEIGHT((*ptree)->right->right);
if(hl < hr){ //右子树的右边高
*ptree = rr_rotate(*ptree); //RR旋转
}else{ //左边高
*ptree = rl_rotate(*ptree); //RL旋转
}
}
(*ptree)->height = MAX(HEIGHT((*ptree)->left),HEIGHT((*ptree)->right))+1; //更新高度
}
return ret; //返回插入结果
}else{ //如果树种已经存在该值
return -1; //那就返回-1
}
}
在我们删除一个节点的时候,如何操作才能让我们的AVL搜索二叉树尽量少失衡或者不失衡呢?
首先我们分以下几种情况来讨论(假设我们已经找到需要删除的节点)。
比如我们需要删除8这个节点,此时节点8的左子树高度高度右子树的高度,我们需要把要被被删除的节点的左子树的最大值节点覆盖掉(即值覆盖)被删除节点,如上图的把节点8的值赋值为7,然后再把7这个节点删除掉(递归调用删除节点函数)
代码实现
if(HEIGHT(node->left)>HEIGHT(node->right)){//左边高
AVLNode *max = node->left; //记录左子树
while(max->right){ //找到左子树中最大值的节点
max = max->right;
}
node->key = max->key; //覆盖
ret = avltree_delete(&(node->left),max->key); //递归删除这个最大值的节点
(*ptree)->height = MAX(HEIGHT((*ptree)->left),HEIGHT((*ptree)->right))+1; //更新高度
如上图,我们需要删除6这个节点,此时被删除的节点6它的右子树高度高于左子树的高度,我们可以用右子树中的最小值的节点来覆盖掉被删除的节点,然后递归调用删除函数用来删除这个最小值的节点。即用7这个节点覆盖掉6这个节点(即值覆盖),然后递归删除7这个节点。
代码实现
}else{
AVLNode *min = node->right;
while(min->left){
min = min->left;
}
node->key = min->key;
ret = avltree_delete(&(node->right),min->key);
(*ptree)->height = MAX(HEIGHT((*ptree)->left),HEIGHT((*ptree)->right))+1;
}
那就直接删除这个节点
if(!node->left && !node->right){
free(*ptree);
*ptree = NULL;
}
把被删除节点的存在的子节点上提,然后删除该节点
AVLNode *denode = *ptree;
*ptree = (*ptree)->left!=NULL?(*ptree)->left:(*ptree)->right;
free(denode);
整体代码实现
int avltree_delete(AVLTree *ptree,int key){
if(!(*ptree)){ //如果被删除的节点不存在,返回-1
return -1;
}
int ret = 0; //记录递归调用的返回值
if((*ptree)->key == key){ //找到要被删除的节点
AVLNode *node = *ptree;
if(node->left && node->right){ //左右节点都存在
if(HEIGHT(node->left)>HEIGHT(node->right)){//左边高
AVLNode *max = node->left;
while(max->right){
max = max->right;
}
node->key = max->key;
ret = avltree_delete(&(node->left),max->key); //递归删除该最大值节点
(*ptree)->height = MAX(HEIGHT((*ptree)->left),HEIGHT((*ptree)->right))+1; //更新高度
}else{
AVLNode *min = node->right;
while(min->left){
min = min->left;
}
node->key = min->key;
ret = avltree_delete(&(node->right),min->key);
(*ptree)->height = MAX(HEIGHT((*ptree)->left),HEIGHT((*ptree)->right))+1;
}
}else if(!node->left && !node->right){ //如果左右节点都不存在
free(*ptree);
*ptree = NULL;
}else{ //如果只存在左节点或者右节点
AVLNode *denode = *ptree;
*ptree = (*ptree)->left!=NULL?(*ptree)->left:(*ptree)->right;
free(denode);
}
return 0;
}else if(key < (*ptree)->key){ //被删除的值小于根节点的值
ret = avltree_delete(&(*ptree)->left,key);
if(ret == 0){
if(HEIGHT((*ptree)->right)-HEIGHT((*ptree)->left) == 2){
int hl = HEIGHT((*ptree)->right->left);
int hr = HEIGHT((*ptree)->right->right);
if(hl<hr){
*ptree = rr_rotate(*ptree);
}else{
*ptree = rl_rotate(*ptree);
}
}
(*ptree)->height = MAX(HEIGHT((*ptree)->left),HEIGHT((*ptree)->right))+1;
}
return ret;
}else{ //被删除的值大于被删除的节点的值
ret = avltree_delete(&(*ptree)->right,key);
if(ret == 0){
if(HEIGHT((*ptree)->left)-HEIGHT((*ptree)->right) == 2){
int hl = HEIGHT((*ptree)->left->left);
int hr = HEIGHT((*ptree)->left->right);
if(hl > hr){
*ptree = ll_rotate(*ptree);
}else{
*ptree = lr_rotate(*ptree);
}
}
(*ptree)->height = MAX(HEIGHT((*ptree)->left),HEIGHT((*ptree)->right))+1;
}
return ret;
}
}
全部源代码