AVL树

前面介绍了二叉查找树,二叉查找树的平均时间复杂度为O(logN),是相当低的,因此是一种非常优异的模型。但是这只是对一个随机输入的二叉查找树有效(平衡的二叉树)。当我们进行一定次数的插入(或删除)后,可能会导致二叉树不平衡,甚至退化成链表,这样会重新使时间复杂度变为O(logN).为此,引入AVL树。

AVL树是Adelson-Velskii和Landis于1962年发明的自平衡二叉树。在一个二叉树中引入平衡条件,最简单的当然是每个节点的左子树和右子树的高度一样。但是这个条件太强了,因此AVL树引入例如下面的一个限制:

“每个节点的左子树的高度和右子树的高度的差的绝对值不超过1”(空树的高度定义为-1)

1)旋转

对于AVL树,除了插入结点外,所有的操作都可以在O(logN)的时间内完成。对于插入节点的情况,插入可能会破坏AVL树的约束条件,而我们可以通过对树的适当旋转来修正。


对一个AVL树插入一个节点,分4中情况

(1)对左子树插入左子节点

(2)对右子树插入左子节点

(3)对左子树插入右子节点

(4)对右子树插入右子节点

其中(1)和(4)是镜面对称情况,(2)和(3)是镜面对称情况,因此他们相应的旋转情况是一致的,因此这里只分析(1)和(2)的情况.

单旋转:

对于(1)的情况适用于单旋转,如上面的图所示,插入节点5,就是在左子树插入左子节点,这个结果直接导致了不符合AVL的约束条件,对于节点7来说,它已经失去了平衡了,节点4,8的失衡也是由于节点7的失衡,因此为了恢复平衡,我们要把节点7的左子树提高一个level,把节点7的右子树降低一个level,因此可以得到上图的旋转后的结果


要旋转之前首先要清晰到底应该是哪个节点导致的不平衡。

对于编程实现来说,旋转本质上是指针的一些改变

从上图可以看到旋转后的树与旋转前的树的高度是一样的。这其实很好理解,正因为增加了一个节点导致树变深了,进而导致了unbalance,所以通过旋转必然要减掉这个深度,否则一样的unbalance.


双旋转:

对应于(2)的情况,单旋转已经不能解决问题了。因此要引入两次但旋转,即双旋转来解决


在这个例子中根节点8是平衡的,不平衡的是节点9,因此要对节点9做双旋转。


另一个例子:


这个例子中根节点6不平衡,因此要对根节点做旋转。

2)编程实现:

AVL树的定义:

struct Avltree;
typedef struct AvlNode *AvlTree;
typedef AvlTree Position;

struct AvlNode
{
	ElementType Element;
	AvlTree Left;
	AvlTree Right;
	int Height;
};

// calculate height
static int Height(Position P)
{
	if(P==NULL)
		return -1;
	else
		return P->Height;
}
插入节点:

AvlTree Insert(Element X,AvlTree T)
{
	if(T==NULL)
	{
		T = malloc(sizeof(struct AvlNode));
		if(T==NULL)
			Error("Out of Space!");
		T->Left = T->Right = NULL;
		T->Element = X;
		T->Height = 0;
	}
	else if(X<T->Element)
	{
		T->Left = Insert(X,T->Left);
		//if X==T->Left,the next if condition is false,it will do nothing
		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;
}
左节点单旋转:


Position SingleRotateWithLeft(Position P1)
{
	Position P2;
	P2 = P1->Left;
	P1->Left = P2->Right;
	P2->Right = P1;
	P1->Height = Max(Height(P1->Left),Height(P1->Right));
	P2->Height = Max(Height(P2->Left),P1->Height);
	return P2;  //new root
}
右节点单循环:(与上面的是镜面对称)

Position SingleRotateWithRight(Position P1)
{
	Position P2;
	P2 = P1->Right;
	P1->Right = P2->Left;
	P2->Left = P1;
	P1->Height = Max(Height(P1->Left),Height(P1->Right));
	P2->Height = Max(Height(P2->Right),P1->Height);
	return P2;  //new root
}
下面是双旋转:其实双旋转的编程实现比较简单,就是把单旋转做两遍,一次左,一次右。

左节点双旋转:



Position DoubleRotateWithLeft(Position P3)
{
	P3->Left = SingleRotateWithRight(P3->Left);
	return SingleRotateWithLeft(P3);
}
右节点双旋转:

Position DoubleRotateWithRight(Position P3)
{
	P3->Right = SingleRotateWithLeft(P3->Right);
	return SingleRotateWithRight(P3);
}
这两个双旋转是镜面对称的。


3)总结:

AVL树是平衡的二叉树,它有限制:“每个节点的左子树的高度和右子树的高度的差的绝对值不超过1”(空树的高度定义为-1)。对AVL树插入值很有可能导致二叉树不再平衡,因此可以通过旋转来消除这种不平衡。旋转分单旋转和双旋转,分别对应于不同的插入情况。最多只需要双旋转。






  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值