YAZONG 我的开源

(数据结构)第四讲-树(4.2平衡二叉树/搜索树)

  , ,
0 评论0 浏览

4.2.1 什么是平衡二叉树

介绍

image.png

A按照自然月份排序。

比较1、2、3月份,分别要比较1、2、3次。这12个月份都要比较。

第三层有3个结点,第四层有3个结点。

这里指平均要花多少时间。平均成功的查找长度。

B按照现在的蓝色排序。

计算同A

C按照英文单词字典顺序。每次插入都比上一个结点要大。

最糟糕,Apr是1次,Sept是12次。

导致的问题,不同的插入顺序,就会形成不同的树结构,这样不同的搜索树,查找效率不同,一个衡量指标叫做平均查找长度。

可以发现,B查找长度最短,相对另外两个,这样的树结构比较好,总体感觉比较平衡,左右两边比较均匀。

一种树均匀的描述方法是两边结点一样,这个要求高,这里要求放低一些,两种指标来衡量:左右两边结点数差不多和左右两边高度差不多(差1),这样认为是基本平衡,叫做平衡二叉树。

image.png

AVL是提出平衡二叉树的科学家以他们名字的首字母来命名的。

注意:这里强调的是 任一结点(包括每个子结点) 的左右子树高度差的绝对值不超过1!

image.png

结点4:左右两边高度差等于0。

结点3:左边高度2,右边0,高度差2,超过了1。

image.png

任何结点的左右子树的高度差绝对值最大为1。

image.png

结点7:左边高度3,右边1,高度差2,超过了1。

AVL树高度与斐(fei)波那契序列

平衡的目的是让树的高度能够低一点,因为越平衡树的高度越低,越不平衡就会往一边倒,导致树的高度会越高。

image.png

上述完全二叉树的高度能达到Log以2为底N的对数,那么平衡二叉树是否也能达到

O(Log以2为底N的对数)呢?

这里的高度是这个路径的一个长度,边的个数。

image.png

高度为0,1个结点。

image.png

高度为1,最少要两个结点,两种情况:一种左右两边都有这个高度为1,另外一种,左边或右边缺一个。

image.png

高度为2,至少需要3层,如拿调左边的结点,那么就不平衡,如拿调右边最下面的结点,高度就不为2。

image.png

Nh = 高度N(h-1)时最少的结点数+高度N(h-2)时最少的结点数+1

上述两个不管哪种情况,总归是左儿子结点数+右儿子结点数。( 同3.2的计算方法 )

上述内容跟下述斐(fei)波那契序列很像,后面的数都是前面两个数的和。

唯一的差别是后面+1。

image.png

image.png

4.2.2 平衡二叉树的调整(左右子树高度绝对值相差1)

比如在对原来的一颗平衡二叉树,做插入删除操作时,变得不平衡了(变成-2或2了),要找到发现者、破坏者和被破坏者(麻烦结点),那么就要做平衡二叉树的调整。

要注意的是,平衡二叉树是一个搜索树或者查找树,后面所有的调整,一定要保证它仍然是一个搜索树或者查找树,而且要保证,左子树比根结点小,右子树比根结点大。

RR插入需要RR(Right Right Rotation)旋转(右单旋)

image.png

平衡被破坏的发现者/被破坏者结点:Mar

破坏者/麻烦结点:Nov

平衡被破坏的发现者/被破坏者结点与破坏者/麻烦结点的关系:破坏者/麻烦结点是在平衡被破坏的发现者/被破坏者结点的右子树的右子树上。

插入:RR插入(插入的结点插在某个结点的 右边/左边 )

旋转:RR旋转(可想象成高中物理的 右手定则 )

( 移动的是橘黄色节点,即:紧挨着破坏者节点的节点。 )

image.png

被破坏的结点跟插入的这个结点是右子树,在右子树上的插入,这个时候做RR旋转,就是把被破坏的右子树拎上来,左边的BL一定比A大,比B小,那么BL要挂在A的右边,B的左边,这样就变平衡了。

RR案例1:添加右子树

image.png

平衡被破坏的发现者/被破坏者结点:5

破坏者/麻烦结点:13

平衡被破坏的发现者/被破坏者结点与破坏者/麻烦结点的关系:破坏者/麻烦结点是在平衡被破坏的发现者/被破坏者结点的右子树的右子树上。

插入:RR插入(插入的结点插在某个结点的 右边/左边 )

旋转:RR旋转(可想象成高中物理的 右手定则 )

RR案例2:添加左子树

image.png

平衡被破坏的发现者/被破坏者结点:5

破坏者/麻烦结点:13

平衡被破坏的发现者/被破坏者结点与破坏者/麻烦结点的关系:破坏者/麻烦结点是在平衡被破坏的发现者/被破坏者结点的右子树的左子树上。

插入:RR插入(插入的结点插在某个结点的 右边/左边 )

旋转:RR旋转(可想象成高中物理的 右手定则 )

LL插入需要LL(Left Left Rotation)旋转(左单旋)

image.png

平衡被破坏的发现者/被破坏者结点:Mar

破坏者/麻烦结点:Apr

平衡被破坏的发现者/被破坏者结点与破坏者/麻烦结点的关系:破坏者/麻烦结点是在平衡被破坏的发现者/被破坏者结点的左子树的左子树上。

插入:LL插入(插入的结点插在某个结点的 右边/左边 )

旋转:LL旋转(可想象成高中物理的 左手定则 )

还要发现一个问题,由于Apr的插入,导致May结点的平衡性也被破坏了,此时,两个结点的平衡性都被破坏了,但是对于现在的调整来讲,只要下面的这样一点平衡了,上面的结点也就平衡了( 跟调试程序编译错误一样,第一个错误改了,后面的错误可能就没了 )。

( 移动的是橘黄色节点,即:紧挨着破坏者节点的节点。 )

image.png

被破坏的结点跟插入的这个结点是左子树,在左子树上的插入,这个时候做LL旋转,就是把被破坏的左子树拎上来,左边的BR一定比B大,比A小,那么BR要挂在B的右边,A的左边,这样就变平衡了。

LR插入需要LR(Double Left Right Rotation)旋转(左-右双旋)

image.png

(注:在前面LL的图形基础上处理)

平衡被破坏的发现者/被破坏者结点:May

破坏者/麻烦结点:Jan

平衡被破坏的发现者/被破坏者结点与破坏者/麻烦结点的关系:破坏者/麻烦结点是在平衡被破坏的发现者/被破坏者结点的左子树的右子树上。

插入:LR插入(插入的结点插在某个结点的 右边/左边 )

旋转:LR旋转(可想象成高中物理的 (先)左手定则和(后)右手定则 )

( 移动的是橘黄色节点,即:紧挨着破坏者节点的节点。 )

image.png

重点关注彩色颜色的圈。

插入的结点无论是插入左结点还是右结点,总归破坏了A的平衡性。

这里做左右旋转的基本模式就是把ABC转换成CBA,因为是平衡二叉/搜索树,所以一定要左边小,右边大,并满足平衡二叉/搜索树的条件。

注:这里的案例,LR和RL的结点是相对称的。

RL插入需要RL(Double Right Left Rotation)旋转(右-左双旋)

image.png

(注:在前面LR的图形基础上处理)

平衡被破坏的发现者/被破坏者结点:Aug

破坏者/麻烦结点:Feb

平衡被破坏的发现者/被破坏者结点与破坏者/麻烦结点的关系:破坏者/麻烦结点是在平衡被破坏的发现者/被破坏者结点的右子树的左子树上。

插入:RL插入(插入的结点插在某个结点的 右边/左边 )

旋转:RL旋转(可想象成高中物理的 (先)右手定则和(后)左手定则 )

( 移动的是橘黄色节点,即:紧挨着破坏者节点的节点。 )

image.png

重点关注彩色颜色的圈。

插入的结点无论是插入左结点还是右结点,总归破坏了A的平衡性。

这里做左右旋转的基本模式就是把ABC转换成CBA,因为是平衡二叉/搜索树,所以一定要左边小,右边大,并满足平衡二叉/搜索树的条件。

注:这里的案例,LR和RL的结点是相对称的。

案例综合(重新计算平衡因子)

(注:在前面RL的图形基础上处理)

image.png

第一行

平衡被破坏的发现者/被破坏者结点:Mar

破坏者/麻烦结点:June

平衡被破坏的发现者/被破坏者结点与破坏者/麻烦结点的关系:破坏者/麻烦结点是在平衡被破坏的发现者/被破坏者结点的左子树的右子树上。

插入:LR插入(插入的结点插在某个结点的 右边/左边 )

旋转:LR旋转(可想象成高中物理的 (先)左手定则和(后)右手定则 )

第二行

平衡被破坏的发现者/被破坏者结点:May

破坏者/麻烦结点:Oct

平衡被破坏的发现者/被破坏者结点与破坏者/麻烦结点的关系:破坏者/麻烦结点是在平衡被破坏的发现者/被破坏者结点的右子树的右子树上。

插入:RR插入(插入的结点插在某个结点的 右边/左边 )

旋转:RR旋转(可想象成高中物理的 右手定则 )

image.png

此节点插入时,平衡二叉树依旧是平衡二叉树,左图中右边的平衡因子都是0,插入之后,平衡因子都变成了-1。

所以说,当插入结点不影响平衡二叉树的平衡性时,树结构是不变的,但是平衡因子还是会改变的。

4.2.3平衡二叉树的旋转与插入(源码)

#include<stdio.h>
int main(void){
	return 0;
}
/*
平衡二叉树AVL的旋转与插入
*/
typedef struct AVLNode *Position;
//AVL树类型
typedef Position AVLTree;
struct AVLNode{
	//结点数据
	ElementType Data;
	//指向左子树
	AVLTree Left;
	//指向右子树
	AVLTree Right;
	//树高度
	int Height;
}
int Max(int a, int b){
	return a > b ? a : b;
}
/*
左单旋
LL旋转
*/
AVLTree SingleLeftRotation(AVLTree A){
	/*
	A必须有一个左子节点B
	将A与B做左单旋,更新A与B的高度,返回新的根结点B.
	*/
	AVLTree B = A->Left;
	A->Left = B->Right;
	B->Right = A;
	A->Height = Max(GetHeight(A->Left), GetHeight(A->Right)) + 1;
	B->Height = Max(GetHeight(B->Left), A->Left) + 1;
	return B;
}
/*
左-右双旋
LR双转
*/
AVLTree DoubleLeftRightRotation(AVLTree A){
	/*
	注意:A必须有一个左子节点B,且B必须有一个右子结点C
	将A、B与C做两次单旋,返回新的根结点C
	*/
	/*
	将B与C做右单旋,C被返回
	*/
	A->Left = SingleRightRotation(A->Left);
	/*
	将A与C做左单旋,C被返回.
	*/
	return SingleLeftRotation(A);
}

/*
右单旋
RR旋转
*/
AVLTree SingleRightRotation(AVLTree A){

}

/*
右-左双旋
RL双转
*/
AVLTree DoubleRightLeftRotation(AVLTree A){

}

/*
将X插入AVL树T中,并且返回调整后的AVL树.
*/
AVLTree Insert(AVLTree T, ElementType X){
	/*
	若插入空树,则新建包含一个结点的树.
	*/
	if(!T){
		T = (AVLTree)malloc(sizeof(struct AVLNode));
		T->Data = X;
		T->Height = 0;
		T->Left = T->Right = NULL;
	}
	/*
	插入T的左子树
	*/
	else if(X < T->Data){
		T->Left = Insert(T->Left, X);
		//如果需要左旋
		if(GetHeight(T->Left) - GetHeight(T->Right) == 2){
			if(X < T->Left->Data){
				//左单旋
				T = SingleLeftRotation(T);
			}
			else{
				//左-右双旋
				T = DoubleLeftRightRotation(T);
			}
		}
	}
	/*
	插入T的右子树
	*/
	else if(X > T->Data){
		T->Right = Insert(T->Right, X);
		//如果需要右旋
		if(GetHeight(T->Left) - GetHeight(T->Right) == -2){
			if(X > T->Right->Data){
				//右单旋
				T = SingleRightRotation(T);
			}
			else{
				//右-左双旋
				T = DoubleRightLeftRotation(T);
			}
		}	
	}
	/*
	插入右子树结束
	else if
	*/
	/*
	无需插入
	else X == T->Data
	*/
	/*
	别忘了更新树高
	*/
	T->Height = Max(GetHeight(T->Left), GetHeight(T->Right)) + 1;

	return T;

}

小总结

image.png


标题:(数据结构)第四讲-树(4.2平衡二叉树/搜索树)
作者:yazong
地址:https://blog.llyweb.com/articles/2023/10/22/1697916492643.html