AVL树

AVL树是一种高度平衡的二分查找树,性质是任何两个子树之间的高度差的绝对值不超过1。我们可以用平衡因子来记录,在每个节点加上一个bf的数据域,记录该节点的左子树高度减去右子树高度的结果。那么,AVL树的每个节点的平衡因子的绝对值就不会超过1。

那么首先,做一些初始化的动作:

/*初始化节点*/
void initOneNode( Node * node, int val ){
	(*node).p = NULL;
	(*node).left = NULL;
	(*node).right = NULL;
	(*node).value = val;
	(*node).bf = 0;
}

接着是树的两个基本操作——左旋、右旋

这是右旋操作,代码如下

/*右旋函数(不考略x左节点为空,在调用函数前判断,未修订平衡因子)*/
void rightRotate( Node ** root, Node * x ){
	Node * y = (*x).left;
	(*x).left = (*y).right;
	if ( (*y).right != NULL ){
		(*(*y).right).p = x;
	}
	(*y).p = (*x).p;
	if ( (*x).p != NULL ){                        //原x不是根节点
		if ( x == (*(*x).p).left ){
			(*(*x).p).left = y;
		}else{
			(*(*x).p).right = y;
		}
	}else{
		*root = y;
	}
	(*y).right = x;
	(*x).p = y;
}

当然,左旋也是类似的

/*左旋函数(不考略x右节点为空,在调用函数前判断,未修订平衡因子)*/
void leftRotate( Node ** root, Node * x ){                                   
	Node * y = (*x).right;
	(*x).right = (*y).left;
	if ( (*y).left != NULL ){
		(*(*y).left).p = x;
	}
	(*y).p = (*x).p;
	if ( (*x).p != NULL ){                        //原x不是根节点
		if ( x == (*(*x).p).left ){
			(*(*x).p).left = y;
		}else{
			(*(*x).p).right = y;
		}
	}else{
		*root = y;
	}
	(*y).left = x;
	(*x).p = y;
}

还有些地方介绍有左右旋之类的,不用在意,也只是这两个基本操作的复合操作而已。


准备工作做好了,那么就开始插入操作

函数头是

void insertAVL( Node ** root, Node * newNode );

首先是二分查找树的插入操作,这个不在这里多说(这里允许重复值插入,默认插在右子树),代码如下

/*二分查找树的插入操作*/
	if ( *root == NULL ){                     //如果插入的是第一个节点
		*root = newNode;
		(**root).bf = 0;
		return;
	}
	Node * tempNode = *root;
	Node * nowNode = tempNode;
	while ( tempNode != NULL ){
		nowNode = tempNode;
		if ( (*newNode).value < (*tempNode).value ){
			tempNode = (*tempNode).left;
		}else{
			tempNode = (*tempNode).right;
		}
	}
	(*newNode).p = nowNode;
	if ( (*newNode).value < (*nowNode).value ){
		(*nowNode).left = newNode;
	}else{
		(*nowNode).right = newNode;
	}
接下来是比较复杂的平衡性修复操作及平衡因子更新操作

思路是这样的,从插入点向根部回溯,若是插在当前节点左子树中,那么当前节点的平衡因子加1,同样,右子树则减1。那么这里可以做一些判断,或是叫做优化:

1、如果当前节点的平衡因子变为0,那么停止操作,因为那说明必定是在当前节点的高度少一的子树上插入节点,所以当前的总高度不变,也就是说对当前节点以上(父、祖父节点等等)的节点没有影响;

2、如果当前节点的平衡因子变为1或-1,当前节点仍处于平衡状态,不需要调整,但以上节点则不一定,所以需要继续回溯;

3、如果当前节点的平衡因子变为2或-2,当前节点不平衡,需要调整;

那么以下找到处理点的代码

nowNode = NULL;
	tempNode = (*newNode).p;
	while ( tempNode != NULL ){
		if ( (*newNode).value < (*tempNode).value ){
			(*tempNode).bf ++;
		}else{
			(*tempNode).bf --;
		}
		if ( (*tempNode).bf == 0 ){              //必定是在节点tempNode的高度少一的子树上插入节点,所以tempNode的总高度不变
			break;
		}else if( (*tempNode).bf == 2 || (*tempNode).bf == -2 ){
			nowNode = tempNode;                                                    //记录当前最小不平衡子树的根节点
			break;
		}
		tempNode = (*tempNode).p;
	}

	if ( nowNode == NULL ){           //平衡,不需要修复
		return;
	}
为什么只需要记录当前最小不平衡子树的根节点,这里可以稍作分析。以当前节点的平衡因子是2为例,2必是从1变来,也就是说插入点位于本来高度较大的子树中,高度差由1变2,那么已当前节点为根的子树的高度无疑会加1,那么(可以先看下面操作)看下面的几种调节方法可知,调节后高度均下降了1(看图最快)。所以插入节点前与,插入节点后并调节后的高度相同,也就是说当前节点的父节点或祖父节点等等平衡因子没有变化,也就不需要继续回溯了。

接下来就是具体怎么做了

1、如果当前节点的平衡因子为2

(1)如果当前节点的左孩子的平衡因子为1,说明在当前节点左孩子节点的左子树上插入了节点

          操作方式是一个右旋就可以了

可以看出C、D、E的平衡因子没有变化,A、B的平衡因子变为零,整棵子树的高度也没有变化,不会影响到上层

if ( (*(*nowNode).left).bf == 1 ){       //说明在当前节点左孩子节点的左子树上插入了节点
			(*nowNode).bf = 0;
			(*(*nowNode).left).bf = 0;
			rightRotate( root, nowNode );
		}

(2)如果当前节点的左孩子的平衡因子为-1,说明在当前节点左孩子节点的右子树上插入了节点

这种情况操作比较简单,先以左孩子为根左旋,再以当前节点为根右旋,但平衡因子调节要分几种情况

         a)当前节点的左孩子的左孩子的右孩子的平衡因子为0


                                (*nowNode).bf = 0;
				(*(*nowNode).left).bf = 0;
 				(*(*(*nowNode).left).right).bf = 0;
        b)当前节点的左孩子的左孩子的右孩子的平衡因子为-1


                                (*nowNode).bf = 0;
				(*(*nowNode).left).bf = 1;
				(*(*(*nowNode).left).right).bf = 0;
        c)当前节点的左孩子的左孩子的右孩子的平衡因子为1


                                (*nowNode).bf = -1;
				(*(*nowNode).left).bf = 0;
				(*(*(*nowNode).left).right).bf = 0;

调节平衡因子之后是旋转操作

                        leftRotate( root, (*nowNode).left );
			rightRotate( root, nowNode );                                 //树的高度减1

2、当前点平衡因子为-2

     这个和2的对称操作就行了


我们可以把平衡调整操作集成到一个函数里面,代码如下

void fixupBlance( Node ** root, Node * nowNode ){
	if ( (*nowNode).bf == 2 ){     //必有左孩子
		if ( (*(*nowNode).left).bf == 0 ){
			(*nowNode).bf = 1;
			(*(*nowNode).left).bf = -1;
			rightRotate( root, nowNode );          //不影响树的高度
		}else if( (*(*nowNode).left).bf == 1 ){
			(*nowNode).bf = 0;
			(*(*nowNode).left).bf = 0;
			rightRotate( root, nowNode );           //树的高度减1
		}else{                      //左孩子平衡因子为-1(左孩子一定有右孩子)
			if ( (*(*(*nowNode).left).right).bf == 0 ){
				(*nowNode).bf = 0;
				(*(*nowNode).left).bf = 0;
				(*(*(*nowNode).left).right).bf = 0;
			}else if( (*(*(*nowNode).left).right).bf == 1 ){
				(*nowNode).bf = -1;
				(*(*nowNode).left).bf = 0;
				(*(*(*nowNode).left).right).bf = 0;
			}else{    //为-1
				(*nowNode).bf = 0;
				(*(*nowNode).left).bf = 1;
				(*(*(*nowNode).left).right).bf = 0;
			}
			leftRotate( root, (*nowNode).left );
			rightRotate( root, nowNode );                                 //树的高度减1
		}
	}else if( (*nowNode).bf == -2 ){     //必有右孩子
		if ( (*(*nowNode).right).bf == 0 ){
			(*nowNode).bf = -1;
			(*(*nowNode).right).bf = 1;
			leftRotate( root, nowNode );          //不影响树的高度
		}else if( (*(*nowNode).right).bf == -1 ){
			(*nowNode).bf = 0;
			(*(*nowNode).right).bf = 0;
			leftRotate( root, nowNode );           //树的高度减1
		}else{                      //右孩子平衡因子为1(右孩子一定有左孩子)
			if ( (*(*(*nowNode).right).left).bf == 0 ){
				(*nowNode).bf = 0;
				(*(*nowNode).right).bf = 0;
				(*(*(*nowNode).right).left).bf = 0;
			}else if( (*(*(*nowNode).right).left).bf == 1 ){
				(*nowNode).bf = 0;
				(*(*nowNode).right).bf = -1;
				(*(*(*nowNode).right).left).bf = 0;
			}else{    //为-1
				(*nowNode).bf = 1;
				(*(*nowNode).right).bf = 0;
				(*(*(*nowNode).right).left).bf = 0;
			}
			rightRotate( root, (*nowNode).right );
			leftRotate( root, nowNode );                                 //树的高度减1
		}
	}
}



那么完整的插入函数为



/*节点插入函数(小则在左边,大于等于在右边)*/
void insertAVL( Node ** root, Node * newNode ){
	/*二分查找树的插入操作*/
	if ( *root == NULL ){                     //如果插入的是第一个节点
		*root = newNode;
		(**root).bf = 0;
		return;
	}
	Node * tempNode = *root;
	Node * nowNode = tempNode;
	while ( tempNode != NULL ){
		nowNode = tempNode;
		if ( (*newNode).value < (*tempNode).value ){
			tempNode = (*tempNode).left;
		}else{
			tempNode = (*tempNode).right;
		}
	}
	(*newNode).p = nowNode;
	if ( (*newNode).value < (*nowNode).value ){
		(*nowNode).left = newNode;
	}else{
		(*nowNode).right = newNode;
	}

	/*更新平衡因子*/
	nowNode = NULL;
	tempNode = (*newNode).p;
	while ( tempNode != NULL ){
		if ( (*newNode).value < (*tempNode).value ){
			(*tempNode).bf ++;
		}else{
			(*tempNode).bf --;
		}
		if ( (*tempNode).bf == 0 ){              //必定是在节点tempNode的高度少一的子树上插入节点,所以tempNode的总高度不变
			break;
		}else if( (*tempNode).bf == 2 || (*tempNode).bf == -2 ){
			nowNode = tempNode;                                                    //记录当前最小不平衡子树的根节点
			break;
		}
		tempNode = (*tempNode).p;
	}

	if ( nowNode == NULL ){           //平衡,不需要修复
		return;
	}
	/*接下来开始修复被破坏的平衡性*/
	fixupBlance( root, nowNode );
};


------------------------------------------------------------------------------------------------------------------------------------------------------------------------

------------------------------------------------------------------------------------------------------------------------------------------------------------------------

接下来就是AVL树的删除了

自然,首先是二分查找树的删除,不做赘述

函数头


void deletetAVL( Node ** root, Node ** deleteNode )



代码



void deletetAVL( Node ** root, Node ** deleteNode ){
	Node * p = NULL;
	p = (**deleteNode).p;
	if ( (**deleteNode).left == NULL && (**deleteNode).right == NULL ){
		if ( p == NULL ){             //该树只有一个节点
			delete *deleteNode;
			*deleteNode = NULL;
			*root = NULL;
			return;
		}
		if ( (**deleteNode).value < (*p).value ){
			(*p).left = NULL;
		}else{
			(*p).right = NULL;
		}
	}else if( (**deleteNode).left != NULL && (**deleteNode).right == NULL ){
		if ( (*p).left == *deleteNode ){        //是父节点的左孩子节点
			(*p).left = (**deleteNode).left;
			(*(**deleteNode).left).p = p;
		}else{
			(*p).right = (**deleteNode).left;
			(*(**deleteNode).left).p = p;
		}
	}else if( (**deleteNode).left == NULL && (**deleteNode).right != NULL ){
		if ( (*p).left == *deleteNode ){        //是父节点的左孩子节点
			(*p).left = (**deleteNode).right;
			(*(**deleteNode).right).p = p;
		}else{
			(*p).right = (**deleteNode).right;
			(*(**deleteNode).right).p = p;
		}
	}else{
		Node * tempNode = findSmallestNode( (**deleteNode).right );
		swapNodeVal( *deleteNode, tempNode );
		*deleteNode = tempNode;
		deletetAVL( root, deleteNode );
		return;
	}

	fixupDeleteAVL( root, p, (**deleteNode).value );   //修复平衡
	delete *deleteNode;
	*deleteNode = NULL;
}



注意到里面有个


fixupDeleteAVL( root, p, (**deleteNode).value );


的修复平衡的操作

那么它就是删除的重点

首先,从删除点开始回溯,逐步修改平衡因子


                if ( val < (*nowNode).value ){
			(*nowNode).bf --;
		}else{
			(*nowNode).bf ++;
		}


接下来的情况有三种:

1、当前节点平衡因子变为-1或1

这时,说明删除节点以前,当前节点平衡因子是0,那么删除一个节点不会影响该树的高度,那么以上节点仍然平衡,而当前节点也平衡(平衡因子不为2或-2),所以不用调节,直接结束


               if ( (*nowNode).bf == 1 || (*nowNode).bf == -1 ){
			return;
		}


2、当前节点平衡因子变为0

当前节点平衡,但以上节点不能保证,所以继续回溯

3、当前节点平衡因子变为2或-2

调用

fixupBlance( root, nowNode );


原理在插入的时候已说明,但是这里稍稍有点区别,


这里可以稍作分析,已当前节点为2为例,2必是从1变来,那么也就是删除了本来高度要低的子树中的节点,导致高度差由1变为2,同时,也说明以当前节点为根的子树的高度删除前后没有变化,都是以子树高度值大的为自己的高度值。也就是说不用向上回溯了。

  bool isOver = false;
		if ( (*nowNode).bf == 2 && (*(*nowNode).left).bf == 0 || (*nowNode).bf == -2 && (*(*nowNode).right).bf == 0 ){
			isOver = true;
		}
		fixupBlance( root, nowNode );
 		if ( isOver ){
			return;
		}

那么修复代码为

/*节点删除修复函数*/
void fixupDeleteAVL( Node ** root, Node * nowNode, int val ){
	while ( nowNode != NULL ){
		if ( val < (*nowNode).value ){
			(*nowNode).bf --;
		}else{
			(*nowNode).bf ++;
		}
		if ( (*nowNode).bf == 1 || (*nowNode).bf == -1 ){
			return;
		}
		bool isOver = false;
		if ( (*nowNode).bf == 2 && (*(*nowNode).left).bf == 0 || (*nowNode).bf == -2 && (*(*nowNode).right).bf == 0 ){
			isOver = true;
		}
		fixupBlance( root, nowNode );
		if ( isOver ){
			return;
		}
		nowNode = (*nowNode).p;
	}
}




07-07
AVL是一种自平衡的二叉查找,它确保了的高度始终保持在对数级别,从而保证了查找、插入和删除操作的时间复杂度为O(log n)。这种数据结构以它的发明者G.M. Adelson-Velsky和E.M. Landis的名字命名[^1]。 ### AVL的定义 - **平衡因子**:每个节点都有一个平衡因子,它是该节点左子的高度去右子的高度。对于AVL来说,所有节点的平衡因子只能是-1, 0或1。 - **空**:如果T是空,则它自然是一个AVL。 - **非空**:若T不是空,则其左右子TL和TR都必须是AVL,并且对于任意节点,|HL - HR| ≤ 1(其中HL和HR分别表示左子和右子的高度)。 ### AVL的操作 当进行插入或删除操作时,可能会破坏AVL的平衡性,这时需要通过旋转来重新恢复平衡: - **单旋转**: - 左单旋(Single Rotate with Left)用于处理左孩子的左子过高。 - 右单旋(Single Rotate with Right)用于处理右孩子的右子过高。 - **双旋转**: - 左右双旋(Double Rotate with Left)用于处理左孩子的右子过高的情况。 - 右左双旋(Double Rotate with Right)用于处理右孩子的左子过高的情况。 这些旋转操作可以保持AVL的性质不变,并且能够快速地调整的结构以维持平衡。 ### AVL的实现 下面是一个简单的C语言代码示例,展示了如何定义AVL的节点以及实现基本的插入操作。这个例子中包含了必要的函数声明和一些核心逻辑。 ```c #include <stdio.h> #include <stdlib.h> // 定义AVL节点结构体 typedef struct AvlNode { int element; struct AvlNode *left; struct AvlNode *right; int height; // 节点的高度 } *AvlTree, *Position; // 获取两个整数中的较大者 int max(int a, int b) { return (a > b) ? a : b; } // 计算给定节点的高度 static int Height(AvlTree T) { if (T == NULL) return -1; // 空节点高度为-1 else return T->height; } // 单旋转 - 左左情况 AvlTree singlerotatewithLeft(AvlTree K2) { Position K1 = K2->left; K2->left = K1->right; K1->right = K2; // 更新节点高度 K2->height = max(Height(K2->left), Height(K2->right)) + 1; K1->height = max(Height(K1->left), K2->height) + 1; return K1; // 新根 } // 单旋转 - 右右情况 AvlTree singlerotatewithRight(AvlTree K2) { Position K1 = K2->right; K2->right = K1->left; K1->left = K2; // 更新节点高度 K2->height = max(Height(K2->left), Height(K2->right)) + 1; K1->height = max(Height(K1->right), K2->height) + 1; return K1; // 新根 } // 双旋转 - 左右情况 AvlTree doublerotatewithLeft(AvlTree K3) { K3->left = singlerotatewithRight(K3->left); return singlerotatewithLeft(K3); } // 双旋转 - 右左情况 AvlTree doublerotatewithRight(AvlTree K3) { K3->right = singlerotatewithLeft(K3->right); return singlerotatewithRight(K3); } // 插入新元素到AVLAvlTree Insert(int x, AvlTree T) { if (T == NULL) { // 如果为空,创建新节点 T = (AvlTree)malloc(sizeof(struct AvlNode)); if (T == NULL) printf("Out of space!!!"); else { T->element = x; T->left = T->right = NULL; T->height = 0; // 新叶节点高度为0 } } else if (x < T->element) { // 向左子插入 T->left = Insert(x, T->left); // 检查并修复平衡 if (Height(T->left) - Height(T->right) == 2) { if (x < T->left->element) T = singlerotatewithLeft(T); // 左左旋转 else T = doublerotatewithLeft(T); // 左右旋转 } } else if (x > T->element) { // 向右子插入 T->right = Insert(x, T->right); // 检查并修复平衡 if (Height(T->right) - Height(T->left) == 2) { if (x > T->right->element) T = singlerotatewithRight(T); // 右右旋转 else T = doublerotatewithRight(T); // 右左旋转 } } // 更新高度 T->height = max(Height(T->left), Height(T->right)) + 1; return T; } ``` 上述代码提供了AVL的基本框架,包括节点定义、插入操作及必要的旋转方法。实际应用中可能还需要添加更多的功能,如删除节点、查找特定值等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值