二叉平衡树(AVL树)的基本操作(c语言)

二叉平衡树(AVL树)的基本操作

前言

二叉查找树的基本操作这篇文章中的插入和删除方法对一颗树进行操作时,难免会出现树形结构向一边倒的情况,比如依次插入1,2,3,4,5就会这样,因为根据我们以前的方法,会一直往右边插入,这样的弊端就是对5节点的操作将至少花费o(n)。

AVL树就是解决这种问题,至于解决问题的方式便是旋转

AVL树的基本操作主要是插入和删除,至于其他操作与二叉查找树一致,如果想了解可以看这篇文章二叉查找树的基本操作

AVL树的插入和删除建立在这棵树原本是AVL树的基础之上的。

预备知识

二叉树中一个节点的高度:一个节点的高度是从该节点到其最远叶子节点的最长路径上的边数。对于一个空节点,它的高度为**-1**,而对于非空节点,它的高度是它左孩子与右孩子之间的最大者再加上1,也就是,treeheight( p) = 1 + Max(treeheight(p->left), treeheight(p->right))

AVL树的定义:AVL树是带有平衡条件的二叉查找树。这个平衡条件是指每个节点的左子树和右子树的高度最多差1的二叉查找树。

平衡因子:通常来说是一个节点的左孩子的高度减去右孩子的高度的值。如果它的绝对值大于1说明树不平衡。

定义

本文规定每一颗树的根节点存储一个正整数值

struct tree
{
    int value;
    struct tree* left;
    struct tree* right;
};//by wqs

一般情况下,定义应该再添加一个height,用来表示此节点的高度,但是如果这样,其他所有函数在执行的时候都必须考虑改变height,这会加大代码的负担,而且偏离了本文对基本操作进行介绍的主题,所以这里采用递归的方式一次次地求height,尽管这会造成大量重复,影响程序运行时间,如果你想避免这种重复,就在原代码的基础上考虑更新每棵树的高度既可。

右单旋操作(因操作(指插入和删除,后面不再说明)了左子树的左子树失去平衡)

如果不说任何废话,其实右单旋操作就是把一颗树状变成另一颗如图所示的树状。

B向右旋转变成根节点,然后让E向右旋转变成A的左子树。这便是右单旋操作。

首先一开始的D节点不一定是一个叶子结点,它也是一棵树。还有一点如果有树是空树(如C结点是空树),那你也把转换后的树(假设对应的C结点)理解为空树既可。

我们来对比一下旋转前和旋转后的树,最大的改变就是D节点向上提拔了A节点向下提拔了,这样D节点原本是高度偏高,向上提拔后D节点虽然高度不变(注意高度的定义),但是新树也就是B节点的右子树相对于之前高度增加,这样D节点就不会偏高了,平衡成功,而且我们还可以看到这样没有改变树的中序遍历,这是很重要的,它维护了二叉查找树的基本性质。其实刚刚我们直接从结论已经证明了这样做的可行性,首先树恢复平衡了,其次它没有破坏二叉查找树的性质,我们的目的已经达到了。

下面是代码实现,接下来的这个函数接受一个已经创建并且要右单旋的树形结构p,(其实就是A节点对应的树),函数直接返回旋转好的树

struct tree* singleright(struct tree* p)
{
    if (p == NULL) return NULL;//空树直接返回
    struct tree* m = p->left;//m就是B节点,P就是A节点
    p->left = m->right;//B的右孩子变成A的左孩子
    m->right = p;//B的右孩子变成A
    return m;//返回B
}//by wqs

左单旋操作(因操作了右子树的右子树失去平衡)

如果不说任何废话,其实左单旋操作就是把一颗树状变成另一颗如图所示的树状。

C向左旋转变成根节点,然后让F向左旋转变成A的右子树。这便是左单旋操作。

右单旋和左单旋是对称的,前面详细说明了右单旋操作,这里便不加赘述了。

下面是代码实现,接下来的这个函数接受一个已经创建并且要左单旋的树形结构p,(其实就是A节点对应的树),函数直接返回旋转好的树

struct tree* singleleft(struct tree* p)
{
    if (p == NULL) return NULL;
    struct tree* m = p->right;//p就是A节点,m就是C节点
    p->right = m->left;//让C的左孩子等于A的右孩子
    m->left = p;//让A变成C的左孩子
    return m;
}//by wqs

右双旋操作(因操作了左子树的右子树失去平衡)

如果不说任何废话,其实右双旋操作就是把一颗树状变成另一颗如图所示的树状。

上面从过程1到过程2,其实是对A的左子树B进行了左单旋操作,过程2到过程3,其实是对A进行了右单旋操作,最终E变成根节点,双旋就是两次单旋

双旋操作在理解了单旋操作后很简单。

原理证明就不说了(可以自己对比一下),反正树恢复平衡了,中序遍历结果也没有变,这就是我们想要的结果,代码也简单到你不敢想。

下面是代码实现,接下来的这个函数接受一个已经创建并且要右双旋的树形结构p,(其实就是A节点对应的树),函数直接返回旋转好的树

struct tree* doubleright(struct tree* p)
{
    if (p == NULL) return NULL;
    p->left = singleleft(p->left);//对A的左子树进行了左单旋
    p = singleright(p);//对A进行了右单旋
    return p;
}//by wqs

左双旋操作(因操作了右子树的左子树失去平衡)

如果不说任何废话,其实左双旋操作就是把一颗树状变成另一颗如图所示的树状。

它和右双旋是对称的,不赘述了。从过程1到过程2,其实是对A的右子树进行了单右旋操作,过程2到过程3,其实是对A进行了单左旋操作,双旋就是两次单旋

下面是代码实现,接下来的这个函数接受一个已经创建并且要左双旋的树形结构p,(其实就是A节点对应的树),函数直接返回旋转好的树

struct tree* doubleleft(struct tree* p)
{
    if (p == NULL) return NULL;
    p->right = singleright(p->right);//对A的右子树进行了单右旋
    p = singleleft(p);//对A进行了单左旋
    return p;
}//by wqs

AVL树的插入操作

首先规定如果树里面已经有了待插入的数,那么我们便什么都不做。

AVL树插入操作第一步就是按二叉查找树的方法正常插入,详情请看二叉查找树的基本操作,下一步便是找到平衡因子的绝对值刚好为2的节点(没什么好疑问的,如果为平衡因子为2的节点平衡了,其他不平衡的节点也平衡了),最后一步根据插入的位置,通过旋转保持平衡。下面举个简单的例子。

上面是一个例子我们要插入1节点,先正常插入,这时树已经失去平衡,然后找到平衡因子的绝对值刚好为2的节点,也就是3节点,我们要调整3节点,由于在3的左子树的左子树插入了1对应右单旋操作因操作了左子树的左子树失去平衡,于是我们要对3节点进行右单旋操作,其他情况的步骤也没有改变,不再一个个地举例子了。

它的原理和旋转的原理一样的,我在第一个旋转操作已经直接根据结论证明了。

如果你还不明白,我给你准备了一个图,执行哪种旋转方式,是由插入节点位于平衡因子绝对值为2的节点的**相对位置来决定的。**下面的圆圈是具体的节点,三角形则是一颗树,x插入到了哪个地方的三角就对平衡因子绝对值为2的树执行相应操作,S指单旋,L指左旋,SL指左单旋,DR指右双旋,依次类推。

下面是代码实现。

接下来的这个函数接受一颗已经创建好的AVL树p,和一个要插入的数x,函数直接返回插入好的树。

struct tree* AVLinsert(struct tree* p, int x)
{
    if (p == NULL)
    {
        p = (struct tree*)malloc(sizeof(struct tree));
		p->value = x;
		p->left = p->right = NULL;
		return p;//正常插入既可
    }
    if (p->value == x) return p;//什么都不做
    else if (p->value > x)
    {
        p->left = AVLinsert(p->left, x);//往左边插入
        if (abs(treeheight(p->left) - treeheight(p->right)) == 2)//从平衡因子绝对值为2开始调整
        {
            if (p->left->value > x) p = singleright(p);//左左对应右单旋
            else p = doubleright(p);//左右对应右双旋
            //如果p->left->value == x,执行两种操作都可以,下面同理
        }
    }
    else if (p->value < x)
    {
        p->right = AVLinsert(p->right, x);//往右边插入
        if (abs(treeheight(p->left) - treeheight(p->right)) == 2)//从平衡因子绝对值为2开始调整
        {
            if (p->right->value > x) p = doubleleft(p);//右左对应左双旋
            else p = singleleft(p);//右右对应左单旋
        }
    }
    return p;
}//by wqs

AVL树的删除操作

对AVL树的删除有一种策略是懒惰删除,但是我们在本文不讨论这种删除方法,想了解的可以自己去查。

我们规定如果树里面没有待删除的树,或者树为空树,我们便什么都不做。

AVL树的删除首先是正常地删除结点,详情请看二叉查找树的基本操作,先看一个简单的例子。

上面这个例子里面,我们要删除5节点,先正常删除,这时4节点发生了不平衡,这时我们看4节点的平衡因子,它的平衡因子为2,说明左子树的高度一点,我们看向4节点的左子树2节点,2节点的平衡因子为0,那么此时无论进行右单旋还是右双旋都是可以的(虽然旋转后树形结构不会长得一样,但是都会是平衡的,都是可行的),如果2节点的平衡因子为1,说明要看向2节点的左节点,也就是1节点,然后对应因操作了左左子树而失去平衡,应该执行右单旋,如果2节点的平衡因子为-1,说明要看向2节点的右节点,也就是3节点,然后对应因操作了左右子树而失去平衡,应该执行右双旋。

反之如果一个节点平衡因子为-2,说明右子树的平衡因子大一点,我们应该看向右子树,如果右子树的平衡因子为1,对应右左左双旋,如果右子树的平衡因子为-1,对应右右左单旋,如果为0则都可以。

它的原理和旋转的原理一样的,我在第一个旋转操作已经直接根据结论证明了。

如果你还不理解我还给你准备了一个表,首先平衡因子是一棵树的左子树的高度减右子树的高度的值。S指单旋,L指左旋,SL指左单旋,DR指右双旋,依次类推。红色是失去平衡(值为2 或者 -2)的节点,黑色是它的孩子节点(值为1 或者 -1 或者 0),注意当值为0,两种操作都可以,但是树的形状改变不一样,只是都可以平衡

下面是代码实现。

接下来的这个函数接受一颗已经创建好的AVL树p,和一个要删除的数x,函数直接返回删除好的树。

struct tree* AVLdelete(struct tree* p, int x)
{
	if (p == NULL) return p;
	if (p->value == x)
	{
		if (p->left != NULL && p->right != NULL)
		{
			struct tree* m = findmin(p->right);
			p->value = m->value;
			p->right = AVLdelete(p->right, p->value);
		}
		else
		{
			struct tree* m = p;
			if (p->left == NULL) p = p->right;
			if (p != NULL && p->right == NULL) p = p->left;
			free(m);
		}//正常删除
	}
	else if (p->value > x)
	{
		p->left = AVLdelete(p->left, x);//往左边删除,左边高度会降低
		if (treeheight(p->left) - treeheight(p->right) == -2)//对应L
		{
			if (p->right != NULL)
			{
				if (treeheight(p->right->left) - treeheight(p->right->right) == 1) p = doubleleft(p);//对应DL
				else p = singleleft(p);//对应SL
			}
		}
	}
	else if (p->value < x)
	{
		p->right = AVLdelete(p->right, x);//往右边删除,右边高度会降低
		if (treeheight(p->left) - treeheight(p->right) == 2)//对应R
		{
			if (p->left != NULL)
			{
				if (treeheight(p->left->left) - treeheight(p->left->right) == 1) p = singleright(p);//对应SR
				else p = doubleright(p);//对应DR
			}
		}
	}
	return p;
}//by wqs

可以看出删除操作的代价还是很昂贵的,如果删除操作相对较少,那么懒惰删除可能是最好的策略。

其他代码

创建树的代码,详情见根据树的遍历结果,创建树

struct tree* creattree(int* pre, int* in, int n)
{
	if (n <= 0) return NULL;
	int* mid = in;
	while (*mid != *pre)
	{
		mid++;
	}
	struct tree* p = (struct tree*)malloc(sizeof(struct tree));
	p->value = *mid;
	p->left = creattree(pre + 1, in, mid - in);
	p->right = creattree(pre + 1 + (mid - in), mid + 1, n - (mid - in) - 1);
	return p;
}//by wqs

返回树的高度的代码

int treeheight(struct tree* p)
{
	if (p == NULL) return -1;
	return 1 + Max(treeheight(p->left), treeheight(p->right));
}//by wqs

返回较大值的代码

int Max(int a, int b)
{
	if (a > b) return a;
	return b;
}//by wqs

判断一棵树是否是AVL树的代码

int isAVLtree(struct tree* p)
{
	if (p == NULL) return 1;
	if (abs(treeheight(p->left) - treeheight(p->right)) > 1) return 0;
	return 1;
}//by wqs

打印二叉树的结构的代码

void preprint(struct tree* p)
{
	if (p == NULL) return;
	printf("%d ", p->value);
	preprint(p->left);
	preprint(p->right);
}
void inprint(struct tree* p)
{
	if (p == NULL) return;
	inprint(p->left);
	printf("%d ", p->value);
	inprint(p->right);
}
void postprint(struct tree* p)
{
	if (p == NULL) return;
	postprint(p->left);
	postprint(p->right);
	printf("%d ", p->value);
}
void structprint(struct tree* p)
{
	printf("这棵树的结构是:\n");
	printf("先序遍历结果:");
	preprint(p);
	printf("\n");
	printf("中序遍历结果:");
	inprint(p);
	printf("\n");
	printf("后序遍历结果:");
	postprint(p);
	printf("\n");
}//by wqs

找最小树的代码

struct tree* findmin(struct tree* p)
{
	if (p == NULL) return p;
	if (p->left != NULL) return findmin(p->left);
	else return p;
}//by wqs

总代码

#include<stdio.h>
#include<stdlib.h>
#include<math.h>
struct tree
{
	int value;
	struct tree* left;
	struct tree* right;
};
struct tree* creattree(int* pre, int* in, int n);
int treeheight(struct tree* p);
int Max(int, int);
int isAVLtree(struct tree* p);
struct tree* singleright(struct tree* p);
struct tree* doubleright(struct tree* p);
struct tree* doubleleft(struct tree* p);
struct tree* singleleft(struct tree* p);
void preprint(struct tree*);
void inprint(struct tree*);
void postprint(struct tree*);
void structprint(struct tree* p);
struct tree* findmin(struct tree*);
struct tree* AVLinsert(struct tree* p, int x);
struct tree* AVLdelete(struct tree* p, int x);
int main()
{
	int n, i = 0, x;
	printf("有几个数:");
	scanf("%d", &n);
	int pre[1000] = { 0 }, in[1000] = { 0 };
	printf("先序遍历结果:");
	for (i = 0; i < n; i++)
	{
		scanf("%d", &pre[i]);
	}
	printf("中序遍历结果:");
	for (i = 0; i < n; i++)
	{
		scanf("%d", &in[i]);
	}
	struct tree* p = creattree(pre, in, n);
	structprint(p);
	if (isAVLtree(p) == 1) printf("这棵树是AVL树\n");
	else
	{
		printf("这棵树不是AVL树,请重新来过\n");
		return 0;
	}
	int key;
	printf("接下来是AVL树操作集:\n输入1插入。\n输入2删除。\n输入3打印一次树的结构。\n输入4免费判断一次树是否为AVL树。\n输入-1退出。\n");
	while (1)
	{
		printf("输入:");
		scanf("%d", &key);
		if (key == -1) break;
		else if (key == 1)
		{
			printf("插入的数字是:");
			scanf("%d", &x);
			p = AVLinsert(p, x);
			printf("插入成功,并且将其旋转为平衡二叉树\n");
		}
		else if (key == 2)
		{
			printf("删除的数字是:");
			scanf("%d", &x);
			p = AVLdelete(p, x);
			printf("删除成功,并且将其旋转为平衡二叉树\n");
		}
		else if (key == 3) structprint(p);
		else if (key == 4)
		{
			if (isAVLtree(p) == 1) printf("这棵树是AVL树\n");
			else
			{
				printf("这棵树不是AVL树,请重新来过\n");
				return 0;
			}
		}
		else printf("输入无效\n");
	}
	return 0;
}
struct tree* creattree(int* pre, int* in, int n)
{
	if (n <= 0) return NULL;
	int* mid = in;
	while (*mid != *pre)
	{
		mid++;
	}
	struct tree* p = (struct tree*)malloc(sizeof(struct tree));
	p->value = *mid;
	p->left = creattree(pre + 1, in, mid - in);
	p->right = creattree(pre + 1 + (mid - in), mid + 1, n - (mid - in) - 1);
	return p;
}
int treeheight(struct tree* p)
{
	if (p == NULL) return -1;
	return 1 + Max(treeheight(p->left), treeheight(p->right));
}
int Max(int a, int b)
{
	if (a > b) return a;
	return b;
}
int isAVLtree(struct tree* p)
{
	if (p == NULL) return 1;
	if (abs(treeheight(p->left) - treeheight(p->right)) > 1) return 0;
	return 1;
}
void preprint(struct tree* p)
{
	if (p == NULL) return;
	printf("%d ", p->value);
	preprint(p->left);
	preprint(p->right);
}
void inprint(struct tree* p)
{
	if (p == NULL) return;
	inprint(p->left);
	printf("%d ", p->value);
	inprint(p->right);
}
void postprint(struct tree* p)
{
	if (p == NULL) return;
	postprint(p->left);
	postprint(p->right);
	printf("%d ", p->value);
}
void structprint(struct tree* p)
{
	printf("这棵树的结构是:\n");
	printf("先序遍历结果:");
	preprint(p);
	printf("\n");
	printf("中序遍历结果:");
	inprint(p);
	printf("\n");
	printf("后序遍历结果:");
	postprint(p);
	printf("\n");
}
struct tree* findmin(struct tree* p)
{
	if (p == NULL) return p;
	if (p->left != NULL) return findmin(p->left);
	else return p;
}
struct tree* singleright(struct tree* p)
{
	if (p == NULL) return NULL;
	struct tree* m = p->left;
	p->left = m->right;
	m->right = p;
	return m;
}
struct tree* singleleft(struct tree* p)
{
	if (p == NULL) return NULL;
	struct tree* m = p->right;
	p->right = m->left;
	m->left = p;
	return m;
}
struct tree* doubleright(struct tree* p)
{
	if (p == NULL) return NULL;
	p->left = singleleft(p->left);
	p = singleright(p);
	return p;
}
struct tree* doubleleft(struct tree* p)
{
	if (p == NULL) return NULL;
	p->right = singleright(p->right);
	p = singleleft(p);
	return p;
}
struct tree* AVLinsert(struct tree* p, int x)
{
	if (p == NULL)
	{
		p = (struct tree*)malloc(sizeof(struct tree));
		p->value = x;
		p->left = p->right = NULL;
		return p;
	}
	if (p->value == x) return p;
	else if (p->value > x)
	{
		p->left = AVLinsert(p->left, x);
		if (abs(treeheight(p->left) - treeheight(p->right)) == 2)
		{
			if (p->left->value > x) p = singleright(p);
			else p = doubleright(p);
		}
	}
	else if (p->value < x)
	{
		p->right = AVLinsert(p->right, x);
		if (abs(treeheight(p->left) - treeheight(p->right)) == 2)
		{
			if (p->right->value > x) p = doubleleft(p);
			else p = singleleft(p);
		}
	}
	return p;
}
struct tree* AVLdelete(struct tree* p, int x)
{
	if (p == NULL) return p;
	if (p->value == x)
	{
		if (p->left != NULL && p->right != NULL)
		{
			struct tree* m = findmin(p->right);
			p->value = m->value;
			p->right = AVLdelete(p->right, p->value);
		}
		else
		{
			struct tree* m = p;
			if (p->left == NULL) p = p->right;
			if (p != NULL && p->right == NULL) p = p->left;
			free(m);
		}
	}
	else if (p->value > x)
	{
		p->left = AVLdelete(p->left, x);
		if (treeheight(p->left) - treeheight(p->right) == -2)
		{
			if (p->right != NULL)
			{
				if (treeheight(p->right->left) - treeheight(p->right->right) == 1) p = doubleleft(p);//对应DL
				else p = singleleft(p);
			}
		}
	}
	else if (p->value < x)
	{
		p->right = AVLdelete(p->right, x);
		if (treeheight(p->left) - treeheight(p->right) == 2)
		{
			if (p->left != NULL)
			{
				if (treeheight(p->left->left) - treeheight(p->left->right) == 1) p = singleright(p);
				else p = doubleright(p);
			}
		}
	}
	return p;
}//by wqs

效果展示

输入和输出

免费送你一棵二叉平衡树(先序遍历+中序遍历),其他的输入可自行发挥

13
10 3 2 7 6 8 13 11 19 17 16 18 23
2 3 6 7 8 10 11 13 16 17 18 19 23

  • 36
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【优质项目推荐】 1、项目代码均经过严格本地测试,运行OK,确保功能稳定后才上传平台。可放心下载并立即投入使用,若遇到任何使用问题,随时欢迎私信反馈与沟通,博主会第一时间回复。 2、项目适用于计算机相关专业(如计科、信息安全、数据科学、人工智能、通信、物联网、自动化、电子信息等)的在校学生、专业教师,或企业员工,小白入门等都适用。 3、该项目不仅具有很高的学习借鉴价值,对于初学者来说,也是入门进阶的绝佳选择;当然也可以直接用于 毕设、课设、期末大作业或项目初期立项演示等。 3、开放创新:如果您有一定基础,且热爱探索钻研,可以在此代码基础上二次开发,进行修改、扩展,创造出属于自己的独特应用。 欢迎下载使用优质资源!欢迎借鉴使用,并欢迎学习交流,共同探索编程的无穷魅力! 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值