平衡二叉树(AVL)的实现

本文深入介绍了AVL树的原理及实现细节,包括其历史背景、平衡条件、旋转操作等内容,并给出了具体的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引言

AVL树是一种改进的二叉排序树,得名于它的发明者 G.M. Adelson-Velsky E.M. Landis(两位俄罗斯数学家)。他们在 1962 年的论文《An algorithm for the organization of information》中发表了它。

原文PDF链接

平衡二叉树(Self-Balancing Binary Search Tree)是一种二叉排序树,其中每一个节点的左子树和右子树的高度差最多等于1。我们将二叉树上结点的左子树森度减去右子树深度的值称为平衡因子(Balance Factor)。当我们插入一个结点时,我们定义,距离插入节点最近且平衡因子绝对值大于1的结点为最小不平衡子树的根结点,即以该结点为根的这个树称为最小不平衡子树


性质

高度为 h 的 AVL 树,节点数 N 最多2^h − 1; 最少 (其中)。
最少节点数 n 如以斐波那契可以用数学归纳法证明:
Nh=F【h+ 2】 - 1 (F【h+ 2】是 Fibonacci polynomial 的第h+2个数)。
即:
N0 = 0 (表示 AVL Tree 高度为0的节点总数)
N1 = 1 (表示 AVL Tree 高度为1的节点总数)
N2 = 2 (表示 AVL Tree 高度为2的节点总数)
Nh=N【h− 1】 +N【h− 2】 + 1 (表示 AVL Tree 高度为h的节点总数)
换句话说,当节点数为 N 时,高度 h 最多为

从斐波那契说起



操作

AVL树的基本操作一般涉及二叉查找树同样的算法。但是要进行预先或随后做一次或多次所谓的"AVL 旋转"。
假设由于在二叉排序树上插入结点而失去平衡的最小子树根结点的指针为a(即a是离插入点最近,且平衡因子绝对值超过1的祖先结点),则失去平衡后进行进行的规律可归纳为下列四种情况:

单向右旋平衡处理RR:由于在*a的左子树根结点的左子树上插入结点,*a的平衡因子由1增至2,致使以*a为根的子树失去平衡,则需进行一次右旋转操作;

单向左旋平衡处理LL:由于在*a的右子树根结点的右子树上插入结点,*a的平衡因子由-1变为-2,致使以*a为根的子树失去平衡,则需进行一次左旋转操作;

双向旋转(先左后右)平衡处理LR:由于在*a的左子树根结点的右子树上插入结点,*a的平衡因子由1增至2,致使以*a为根的子树失去平衡,则需进行两次旋转(先左旋后右旋)操作。

双向旋转(先右后左)平衡处理RL:由于在*a的右子树根结点的左子树上插入结点,*a的平衡因子由-1变为-2,致使以*a为根的子树失去平衡,则需进行两次旋转(先右旋后左旋)操作。



代码示例

下面的代码实现了AVL的插入操作

#include<iostream>
#include<cstdio>
#include<stdlib.h>
using namespace std;
#define LH +1
#define EH 0
#define RH -1
#define TRUE 1
#define FALSE 0  
   
typedef int Status; 

typedef struct BiTNode{
	int data;
	int bf;
	struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;


//对以P为根的二叉排序树作右旋处理
//处理之后p指向新的树根结点,即旋转处理之前的左子树根结点 
void R_Rotate(BiTree *p)
{
	BiTree L;
	L=(*p)->lchild;//L指向p的左子树根结点 
	(*p)->lchild=L->rchild;//L的右子树挂接为P的左子树 
	L->rchild=(*p);
	*p=L;//p指向新的根节点 
}

//对以P为根的二叉排序树作左旋处理
//处理之后p指向新的树根结点,即旋转处理之前的右子树根结点
void L_Rotate(BiTree *p)
{
	BiTree R;
	R=(*p)->rchild;//R指向p的右子树根结点 
	(*p)->rchild=R->lchild;//R的左子树挂接为P的右子树 
	R->lchild=(*p);
	*p=R;//p指向新的根节点 
}

//对以指针T所指结点为根的二叉树作左平衡旋转处理
//本算法结束时,指针T指向新的根结点
void LeftBalance(BiTree *T)
{
	BiTree L,Lr;
	L=(*T)->lchild;//L指向T的左子树根结点 
	switch (L->bf)
	{//检查T的左子树的平衡度,并作相应平衡处理 
		case LH://新结点插入在T的左孩子的左子树上,要作单右旋处理 
			(*T)->bf=L->bf=EH;
			R_Rotate(T);
			break;
		case RH://新结点插入在T的左孩子的右子树上,要作双旋处理 
			Lr=L->rchild;//Lr指向T的左孩子的右子树根 
			switch(Lr->bf)//修改T及其左孩子的平衡因子 
			{
				case LH:
					(*T)->bf=RH;
					L->bf=EH;
					break;
				case EH:
					(*T)->bf=L->bf=EH;
					break;
				case RH:
					(*T)->bf=EH;
					L->bf=LH;
					break;
			}
		Lr->bf=EH;
		L_Rotate(&(*T)->lchild);//对T的左子树作左旋平衡处理 
		R_Rotate(T);//对T作右旋平衡处理 
	}
} 


//对以指针T所指结点为根的二叉树作右平衡旋转处理
//本算法结束时,指针T指向新的根结点
void RightBalance(BiTree *T)
{
	BiTree R,Rl;
	R=(*T)->rchild;//R指向T的右子树根结点 
	switch (R->bf)
	{//检查T的左子树的平衡度,并作相应平衡处理 
		case RH://新结点插入在T的右孩子的右子树上,要作单右旋处理 
			(*T)->bf=R->bf=EH;
			L_Rotate(T);
			break;
		case LH://新结点插入在T的右孩子的左子树上,要作双旋处理 
			Rl=R->lchild;//Rl指向T的右孩子的左子树根 
			switch(Rl->bf)//修改T及其左孩子的平衡因子 
			{
				case RH:
					(*T)->bf=LH;
					R->bf=EH;
					break;
				case EH:
					(*T)->bf=R->bf=EH;
					break;
				case LH:
					(*T)->bf=EH;
					R->bf=RH;
					break;
			}
		Rl->bf=EH;
		R_Rotate(&(*T)->rchild);//对T的右子树作右旋平衡处理 
		L_Rotate(T);//对T作左旋平衡处理 
	}
}


//若在平衡的二叉排序树T中不存在和e有相同关键字的结点
//则插入一个数据元素为e的新结点并返回1,否则返回0
//若因插入而使二叉排序树失去平衡,则作平衡旋转处理
//变量taller反映T长高与否
Status InsertAVL(BiTree *T,int e,Status *taller)
{
	if(!*T)
	{
		//插入新结点,树长高,置taller为TRUE
		*T=(BiTree)malloc(sizeof(BiTNode));
		(*T)->data=e;
		(*T)->lchild=(*T)->rchild=NULL;
		(*T)->bf=EH;
		*taller=TRUE; 
	}
	
	else
	{
		if(e==(*T)->data)
		{//树中已存在和e有相同关键字的结点则不再插入 
			*taller=FALSE;
			return FALSE; 
		}
		
		if(e<(*T)->data)
		{//应继续在T的左子树中进行搜索 
			if(!InsertAVL(&(*T)->lchild,e,taller))//未插入 
				return FALSE;
			if(*taller)//已插入到T的左子树中且左子树长高 
			{
				switch((*T)->bf)//检查T的平衡度 
				{
					case LH://原本左子树比右子树高,需要作左平衡处理
						LeftBalance(T);
						*taller=FALSE;
						break;
					case EH://原本左右子树登高,现因左子树增高而树增高 
						(*T)->bf=LH;
						*taller=TRUE;
						break;
					case RH://原本右子树比左子树高,现在左右子树等高 
						(*T)->bf=EH;
						*taller=FALSE;
						break;
				}
			}
		}
		
		else 
		{//应继续在T的右子树中进行搜索 
			if(!InsertAVL(&(*T)->rchild,e,taller))//未插入
				return FALSE;
			if(*taller)//已插入到T的右子树且右子树“长高” 
			{
				switch((*T)->bf)//检查T的平衡度 
				{
					case LH://原本左子树比右子树高,现在左、右子树等高
						(*T)->bf=EH;
						*taller=FALSE;
						break;
					case EH://原本左右子树等高,现因右子树增高而树增高
						(*T)->bf=RH;
						*taller=TRUE;
						break;
					case RH://原本右子树比左子树高,需要作右平衡处理 
						RightBalance(T);
						*taller=FALSE;
						break; 
				}
			} 
		}
	}
	return TRUE;
} 


//中序输出  
void InOrder(BiTree T)  
{  
    if(T)  
    {  
        InOrder(T->lchild);  
        printf("%d ",T->data);  
        InOrder(T->rchild);  
    }  
    else return;  
}  
  
//前序输出  
void PreOrder(BiTree T)  
{  
	if (!T) return ;
    else{
        printf("%d ",T->data);  
        PreOrder(T->lchild);  
        PreOrder(T->rchild);
	}
}  

int main()
{
	int i;
	int a[16]={3,2,1,4,5,6,7,16,15,14,13,12,11,10,8,9};
	BiTree T=NULL;
	Status taller;
	for(int i=0;i<16;++i){
		InsertAVL(&T,a[i],&taller);
	}
	printf("当前的二叉树前序和中序序列为:\n");  
    PreOrder(T);  
    printf("\n");  
    InOrder(T);  
    printf("\n");  
    return 0;
}





结语

二叉排序树还有另外的平衡算法,如红黑树(Red and Black Tree)等,与平衡二叉树(AVL)相比各有优势


红黑树并不追求完全的平衡------它只要求部分地达到平衡要求,降低了对旋转的要求从而提高了性能。


另外,如果数据玩全是静态的,做一个哈希表,性能可能会更好一些。



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值