AVL树

白昼怎懂夜的黑 提交于 2019-11-29 21:11:17

AVL树这样一棵搜索二叉树,它的左右子树的深度之差不超过1。因此,他是带有条件的搜索二叉树。这个条件保证了AVL树的深度是O(log n).最简单的想法是左右两棵子树保持相同的高度。但是这种条件过于苛刻,难以使用。AVL只要求深度之差不超过1。AVL解决了二叉搜索树带来的不平衡问题。但是要求变成了我们必须在每次操作后进行调整,以使得AVL树保持平衡。另一种较新的方法是放弃平衡条件,允许树有任意的深度,但是在每次操作后要进行调整,以使得后面的操作效率更高。有一种这样的树称之为伸展树。

在AVL树的每一个节点中保留其高度信息是必须的。在一棵高度为h的AVL树中,最少节点数S(h) = S(h-1)+S(h-2)+1。对于h为0时,S(h)=1;h为2时,S(h)=2。这个函数与斐波那契数列密切相关。

如果一个插入操作破坏了AVL树的平衡条件,那么这插入后形成的左右子树的高度之差最大是2。因此只会出现4种情形。

  1. 对节点的左儿子的左子树进行一次插入;
  2. 对节点的右儿子的右子树进行一次插入;
  3. 对节点的左儿子的右子树进行一次插入;
  4. 对节点的右儿子的左子树进行一次插入;

上述4种情形之中,1和2是镜像对称的,3和4是镜像对称的。所以看起来只有两种情况,但是对于编程实现而言还是4种情形。对于1,2这样的插入操作,可以通过单旋转来完成;对于3,4这样的插入操作,需要通过稍微复杂的双旋转操作来完成。

单旋转:插入之前的高度是和插入之后的高度是保持一致的。插入操作不仅仅是修改局部的变化,树的其余部分也必须知道这个变化。插入可能会导致多个节点的平衡被破坏,但是我们只需要修复距离这个插入节点最近的被破坏平衡的节点。

双旋转:对于1,2两种情形,如果采用单旋转来做,那么会发现,单旋转并没有降低树的深度。它还是不平衡的。双旋转解决了这个问题,它等价于做了两次单旋转。

插入以后树可能还是平衡的,这时候不需要调整树的结构,但是我们仍然需要更新树的深度。

代码实现如下

AVL树的ADT:(avl.h文件)

#ifndef AVLTREE
#define AVLTREE

#include<stdio.h>
#include<stdlib.h>
#define MAX(a,b) ((a) > (b) ? (a) : (b))

typedef int ELementType;
typedef struct AVLNode *Position;
typedef Position AVLTree; /* AVL树类型 */

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

AVLTree Insert(ELementType x, AVLTree T);
int Height(Position P);
AVLTree LLRotate(Position K);
AVLTree LRRotate(Position K);
AVLTree RRRotate(Position K);
AVLTree RLRotate(Position K);
void InorderTraversal(AVLTree T);

#endif // AVLTREE

main.cpp文件:(测试流程)在二叉搜索树中已经实现了绝大多数的查找树操作,例如前序遍历,中序遍历,后序遍历,求最大,最小值等。在AVL树中就不一一实现了,只就插入做了实现,我对删除采用的是懒惰删除法。在此不在说明。只测试一下AVL树的深度是不是O(log n)以及中序遍历输出是不是有序的。这些足以证明它就是我们要求的AVL树。

#include"avl.h"

int main()
{
	AVLTree avltree = NULL;
	printf("AVL树元素插入顺序如下:");
	for (int i = 5; i > 0 ; i--)
	{
		printf("%d ",i);
		avltree = Insert(i, avltree);
	}
	for (int i = 6; i < 10; i++)
	{
		printf("%d ", i);
		avltree = Insert(i, avltree);
	}
	printf("\n中序遍历输出:");
	InorderTraversal(avltree);
	printf("\nAVL树的高度:%d\n",Height(avltree));
	system("pause");
	return 0;
}

alv.cpp文件

#include "avl.h"

AVLTree Insert(ELementType x, AVLTree T)
{
	if (NULL == T)	//T为空,创建树
	{
		T = (AVLTree)malloc(sizeof(struct AVLNode));
		if (NULL == T)
		{
			printf("创建树失败!");
		}
		else
		{
			T->Data = x;
			T->Height = 0;		//树根深度为0
			T->Left = NULL;
			T->Right = NULL;
		}
	}
	else
	{
		if (x < T->Data)		//x小于根节点数据
		{
			T->Left = Insert(x, T->Left);	//递归插入左子树
			if (Height(T->Left) - Height(T->Right) == 2)	//AVL树不平衡了,调整一下
			{
				if (x < T->Left->Data)		
				{
					T = LLRotate(T);		//插入到左子树的左子树
				}
				else
				{
					T = LRRotate(T);		//插入到左子树的右子树
				}
			}
		}
		else if (x > T->Data)
		{
			T->Right = Insert(x, T->Right);		//递归插入右子树
			if (Height(T->Right) - Height(T->Left) == 2)		//AVL树不平衡了,调整一下
			{
				if (x > T->Right->Data)
				{
					T = RRRotate(T);		//插入右子树的右子树
				}
				else
				{
					T = RLRotate(T);		//插入右子树的左子树
				}
			}
		}
		else
		{
			//x在这棵AVL树中,我们什么都不做,当然,我们也可以重新设计AVL树的ADT。
			//使得ADT可以保存数据出现的次数,如果有相同数据插入,我们就使得次数加1。
			//这样的做法为我们在AVL树中做一个删除也提供了一种方式,即:懒惰删除。
			//我们并不将这个节点从树中删除,而只是去更改数据出现的次数减1。
			//这样我们就不需要做多余的操作去调整可能出现的不平衡状态。
			//这种做法在有重复关键字时很好用。并且这种做法只会导致树的高度略微上升。
			//这样的做法使得删除变得非常快,并且如果被删除的元素重新插入,那么省去
			//了重新申请空间的开销。
		}
		T->Height = MAX(Height(T->Left), Height(T->Right)) + 1;		//更新树的高度
	}
	return T;
}

int Height(Position P)		//AVL树的节点保存了高度这一信息,直接返回即可
{
	if (NULL == P)
	{
		return 0;		//节点为空,定义高度为0
	}
	else
	{
		return P->Height;
	}
}

AVLTree LLRotate(Position K)
{
	Position M;
	//调整AVL树
	M = K->Left;
	K->Left = K->Left->Right;
	M->Right = K;

	K->Height = MAX(Height(K->Left), Height(K->Right)) + 1;		//更新右子树的高度
	M->Height = MAX(Height(M->Left), Height(M->Right)) + 1;		//更新新的根节点高度
	//M->Height = MAX(Height(M->Left), K->Height) + 1;     Height(M-Right) == K->Height
	
	return M;
}

AVLTree LRRotate(Position K)
{
	//这里设计的很巧妙,通过先旋转K的左子树。然后在整体旋转K。
	K->Left = RRRotate(K->Left);
	return LLRotate(K);
}

AVLTree RRRotate(Position K)
{
	Position M;
	//调整AVL树
	M = K->Right;
	K->Right = M->Left;
	M->Left = K;

	K->Height = MAX(Height(K->Left), Height(K->Right)) + 1;	//更新左子树的高度
	M->Height = MAX(Height(M->Right), Height(M->Left)) + 1;	//更新新的根节点高度
	//M->Height = MAX(Height(M->Left), K->Height) + 1;     Height(M-Right) == K->Height

	return M;
}

AVLTree RLRotate(Position K)
{
	K->Right = LLRotate(K->Right);
	return RRRotate(K);
}

void InorderTraversal(AVLTree T)
{
	if (T)
	{
		InorderTraversal(T->Left);
		printf("%d ", T->Data);
		InorderTraversal(T->Right);
	}
}

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!