数据结构-平衡二叉树

Posted yoshi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构-平衡二叉树相关的知识,希望对你有一定的参考价值。

对于一般的二叉搜索树,搜索树结点不同插入次序,将导致不同的深度和平均查找长度ASL。甚至在极端的情况下,二叉搜索树会退化称线性的链表,导致插入和查找的复杂度下降到O(n),所以便提出了平衡二叉树的概念。

基本概念

平衡因子(Balance Factor, BF):BF(T)=hL-hR,其中hL、hR分别是T的左、右子树的高度
平衡二叉树(Balanced Binary Tree, AVL树):一棵二叉树,可以为空;或者任一结点左、右子树高度差的绝对值不超过1,即|BF(T)|<=1

给定结点数为n的AVL树的最大高度为O(logn)

抽象数据类型描述

类型名称:平衡二叉树
数据对象集:一棵二叉树,任一结点左、右子树高度差的绝对值不超过1
操作集:

  • Position Rotation(Position A):二叉树位置A的平衡调整
  • AVLTree Insert(AVLTree T, ElementType X):在平衡二叉树T中插入元素X

结构体定义

typedef struct AVLNode *Position;
typedef Position AVLTree;
struct AVLNode{
	ElementType Data;		//结点数据
	AVLTree Left;		//指向左子树
	AVLTree Right;		//指向右子树
	int Height;			//树的高度
};

平衡二叉树的调整

在插入一个新的结点后,那些从插入点到根结点的路径上的结点的平衡可能会被改变,导致平衡二叉树的不平衡。
我们把平衡因子大于1且高度最低(深度最深)的结点叫做不平衡的“发现者”,把插入后导致不平衡的新结点叫做“麻烦结点”。那么,不平衡一共有4种不同的情况:

  1. 对发现者的左孩子的左子树进行一次插入
  2. 对发现者的左孩子的右子树进行一次插入
  3. 对发现者的右孩子的左子树进行一次插入
  4. 对发现者的右孩子的右子树进行一次插入

其中,情况1和4是关于不平衡发现者的镜像对称,而2和3是关于发现者的镜像对称。前一种情况是插入发生在“外边”的情况(即左-左或右-右),该情况通过对树的一次单旋转完成调整。后一种情况是插入发生在“内部”的情况(即左-右或右-左),该情况通过稍微复杂些的双旋转来处理。

LL旋转

对于情况1,麻烦结点是发现者的子树的孩子,需要LL旋转来调整(实际上旋转方向是右旋转)。

int Max(int a, int b){
	return a>b?a:b;
}

int GetHeight(Position P){
	if(P==NULL)
		return -1;
	else
		return P->Height;
}

Position SingleLeftRotation(Position A){		//A必须要有左子结点B
	//将A与B做左单旋,更新A与B的高度,返回新的根结点B
	Position 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->Height)+1;
	return B;
}
RR旋转

对于情况4,麻烦结点是发现者的子树的孩子,需要RR旋转来调整(实际上旋转方向是左旋转)。

Position SingleRightRotation(Position A){		//A必须要有右子结点B
	//将A与B做右单旋,更新A与B的高度,返回新的根结点B
	Position B=A->Right;
	A-Right=B->Left;		//开始旋转
	B->Left=A;
	A->Height=Max(GetHeight(A->Left),GetHeight(A->Right))+1;		//更新高度
	B->Height=Max(A->Height,GetHeight(B->Right))+1;
	return B;
}
LR旋转

对于情况2,麻烦结点是发现者的子树的孩子,需要LR旋转来调整(先RR旋转,再LL旋转)

Position DoubleLeftRightRotation(Position A){		//A必须要有左子结点B,B必须要有右子结点C
	//将A、B与C做两次单旋,返回新的根结点C
	A->Left=SingleRightRotation(A->Left);		//将B与C做RR旋转(右单旋),C被返回
	return SingleLeftRotation(A);		//将A与C做LL旋转(左单旋),C被返回
}
RL旋转

对于情况3,麻烦结点是发现者的子树的孩子,需要RL旋转来调整(先LL旋转,再RR旋转)

Position DoubleRightLeftRotation(Position A){		//A必须要有右子结点B,B必须要有左子结点C
	//将A、B与C做两次单旋,返回新的根节点C
	A->Right=SingleLeftRotation(A->Right);		//将B与C做LL旋转(左单旋),C被返回
	return SingleRightRotation(A);		//将A与C做RR旋转(右单旋),C被返回
}

插入

在平衡二叉树中插入新结点,通过比较新元素与原有结点元素的大小,确定插入位置(通过递归),创建新结点,在调整平衡二叉树(上文4种情况)。

AVLTree Insert(AVLTree T, ElementType X){		//将X插入AVL树T中,并返回调整后的AVL树
	if(T==NULL){
		T=(AVLTree)malloc(sizeof(struct AVLTree));		//空子树,则创建一个结点的树
		T->Data=X;
		T->Left=T->Right=NULL;
		T->Height=0;
	}
	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的左孩子的左结点
				T=SingleLeftRotation(T);		//左单旋
			else			//插入结点是T的左孩子的右结点
				T=DoubleLeftRightRotation(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的右孩子的右结点
				T=SingleRightRotation(T);		//右-左双旋
			else			//插入结点是T的右孩子的左结点
				T=DoubleRightLeftRotation(T);		//右单旋
	}
	//else X==T->Data,什么也不做
	T->Height=Max(GetHeight(T->Left),GetHeight(T->Right))+1;		//更新树高
	return T;
}




以上是关于数据结构-平衡二叉树的主要内容,如果未能解决你的问题,请参考以下文章

求数据结构算法平衡二叉树实现代码

数据结构与算法二叉树——平衡二叉树

平衡二叉树的删除

数据结构手写平衡二叉树(AVL)

平衡二叉树代码

笔试面试题目:平衡二叉树的判断