AVL搜索二叉树C语言实现

什么是AVL搜索二叉树?
AVL树本质上还是一棵二叉搜索树,它的特点是:
1.本身首先是一棵二叉搜索树。
2.带有平衡条件:每个结点的左右子树的高度之差的绝对值(平衡因子)最多为1。
也就是说,AVL树,本质上是带了平衡功能的二叉查找树(二叉排序树,二叉搜索树)。

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);

函数代码实现

接下来我们把这些函数一个一个的实现

1.判断这个AVL搜索二叉树是否为空?

bool avltree_is_empty(AVLTree tree){	//判断是否为空
	return tree == NULL;			//只需要判断根节点是否为NULL即可
}

2.获得AVL搜索二叉树的节点个数

我们选择用递归的方式来实现:节点个数=本身+左子树的节点个数+右子树的节点个数

size_t avltree_size(AVLTree tree){
	if(!tree){
		return 0;
	}
	return avltree_size(tree->left)+avltree_size(tree->right) + 1;

3.获得AVL搜索二叉树的高度

AVL树的高度即等于根节点的高度

size_t avltree_height(AVLTree tree){
	if(!tree){
		return 0;	
	}	
	return tree->height;
}

4.查找AVL树中是否有某个元素,有返回ture,没有返回NULL

选择用递归的方法:如果根的值等于搜索的值,成功找到,如果搜索的元素小于根的值去左子树找,如果大于就去右子树找

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);	
	}
}

5.中序遍历

先遍历左子树,再遍历根节点,最后遍历右子树,并通过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);
	}	
}

6.层序遍历

先遍历第一层,再一层一层的往下遍历
我们需要使用到队列这个数据结构,我先提供队列的函数,并不多做讲解:

#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);	
		}
	}
}

7.销毁AVL搜索二叉树,回收内存

先递归的销毁左子树和右子树,再销毁根节点

void avltree_destroy(AVLTree tree){
	if(!tree){
		avltree_destroy(tree->left);
		avltree_destroy(tree->right);
		free(tree);
	}	
	tree = NULL;
}

8.AVL搜索二叉树的节点插入

由于AVL搜索二叉树的条件是要求平衡,即左右子树高度差不超过1,所以我们在插入一个节点后,该树的平衡性可能会遭到破坏,此时为了同时保持平衡性质和搜索树的性质,我们需要对插入节点的局部树进行旋转。

(1)LL旋转:

在根节点的左子树的左节点下面插入一个节点:会导致失衡
LL旋转
代码实现

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旋转来理解这些代码的含义,意思都相同,就是方向不同而已,所以不多做累述)

(2)RR旋转

在根节点的右子树的右节点后面插入一个节点会导致失衡
RR旋转
代码实现

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;
}

(3)LR旋转

在根节点的左子树的右节点的后面插入一个节点,导致失衡,我们发现其实只需要对根节点的左子树进行一次RR旋转,再对根进行一次LL旋转即可。
LR旋转
代码实现

static AVLNode* lr_rotate(AVLTree tree){
	tree->left = rr_rotate(tree->left);
	return ll_rotate(tree);
}

(4)RL旋转

在根节点的右子树的左节点的后面插入一个节点,导致失衡,我们发现其实只需要对根节点的右子树进行一次LL旋转,再对根进行一次RR旋转即可。
RL旋转
代码实现

static AVLNode* rl_rotate(AVLTree tree){
	tree->right = ll_rotate(tree->right);
	return rr_rotate(tree);
}

(5)我们再写一个插入节点的函数(初始化插入的节点)

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
	}
}

9.AVL搜索二叉树的删除节点

在我们删除一个节点的时候,如何操作才能让我们的AVL搜索二叉树尽量少失衡或者不失衡呢?
首先我们分以下几种情况来讨论(假设我们已经找到需要删除的节点)。

(1)该节点的左右节点都存在

1.左边高(被删除的节点的左子树高度大于右子树高度)

情况1
比如我们需要删除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;	//更新高度
2.右边高(被删除的节点的右子树高度大于左子树高度)

情况2
如上图,我们需要删除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;
	}

(2)该节点的左右子树都不存在

那就直接删除这个节点

if(!node->left && !node->right){
	free(*ptree);
	*ptree = NULL;
}

(3)该节点的左右子树存在一个

把被删除节点的存在的子节点上提,然后删除该节点

AVLNode *denode = *ptree;
*ptree = (*ptree)->left!=NULL?(*ptree)->left:(*ptree)->right;
free(denode);

(4)如果被删除的值小于根节点的值,去左子树中删除

(5)如果被删除的值大于根节点的值,去右子树中删除

整体代码实现

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;
	}
}

最后,一个简单地AVL的一些基本操作实现函数就写好了

全部源代码

发布了14 篇原创文章 · 获赞 84 · 访问量 2796

猜你喜欢

转载自blog.csdn.net/weixin_42617375/article/details/103432247