非线性数据结构 之 AVL树(1)

之前讨论过BST树,BST树不是一种平衡树,什么叫平衡树呢? 所谓的平衡指的是一棵树的左右两棵子树的高度差,如果高度差小于等于1,我们就认为是平衡的,否则就是不平衡的,试想一下,如果按顺序插入1,2,3,4,5,6,7这么几个数的话,就会构造出一棵右倾的BST树,所有的子节点都是其父节点的右孩子,这样的BST的搜索查找性能就被退化成线性的了。所以树的平衡性是衡量一棵树的最重要的指标。

平衡的BST树有很多不同的算法来实现,其中最著名的是所谓的AVL树,所以AVL树就是一种平衡二叉搜索树,下面来讨论它。

怎么维持一棵树的平衡呢? 根据定义,平衡就是要维持任意一棵树中的子树高度差小于等于1,我们在设计节点的时候,增加一个成员:树的高度。代码如下:

typedef struct _tree_node
{
	tn_datatype data;
	struct _tree_node *lchild;
	struct _tree_node *rchild;

#ifdef BALANCE_FACTOR
	int bf; // for AVL
#endif

}treenode, *linktree;

节点设计中,除了有左右孩子指针之外,增加了一个bf成员,这个成员一般被称为平衡因子,其实就是记录以该节点为根节点的子树的高度。

一棵树怎么会失去平衡呢?请看:

《非线性数据结构 之 AVL树(1)》

上图是一个典型的插入一个节点导致BST树失去平衡的例子,在框框中的子树里,对于根节点10来说,其左右子树的高度差已经达到2了,因此根据定义这不是一棵平衡树了,为了让一棵树保持平衡,我们首先总结所有出现不平衡的情况,再来看如何针对每一种情况使得二叉树重新得到平衡。

那么插入节点之后失去平衡的情况是怎么样的呢? 其实,情况总结起来只有两种,细分为四种,一图顶万言:

《非线性数据结构 之 AVL树(1)》

对于一棵二叉树,插入一个节点(黑色的新节点)导致不平衡的情况就是上图所示的四种情况,四种情况是对称的,所以只要理解了LL不平衡和LR不平衡,那么RR不平衡和RL不平衡都是一样的。

所谓的L(左)不平衡指的是,在插入之前,根节点(注意,树中的任意一个节点都可以是根节点,根节点是相对的)的左右子树高度差已经是1了(左子树高度 – 右子树高度),于是插入一个节点到红色根节点的左子树后,其左右子树的高度差就可能达到2。如果左右子树高度差确实达到2,那么情况分为两种:

第一,插入的节点比其左子树的根节点小引起的不平衡,叫做LL不平衡,LL就是Left-Left,也就是说这种不平衡是由左边的子树的左子树高度增加导致的,请对照图看。

第二,插入的节点比其左子树的根节点大引起的不平衡,叫做LR不平衡,LL就是Left-Right,也就是说这种不平衡是由左边的子树的右子树高度增加导致的,请对照图看。

对称地,对于R(右)不平衡来说,也分为RR不平衡和RL不平衡,就是插入的节点在根节点的右子树的右边,和插入的节点在根节点的右子树的左边。

下面来讨论如何重新平衡二叉搜索树,我们一个一个来看,先来看看LL不平衡:

 《非线性数据结构 之 AVL树(1)》

从图可以看到,对于LL不平衡,我们只需要对发生不平衡的根节点(红色节点)进行一次“右旋转”即可,旋转之后,二叉树重新保持了平衡,而且红,白,黑三个节点的大小关系仍然保持,不会破坏二叉搜索树的定义。

那么这个所谓的右旋转是怎么实现的呢??看清楚一点你会发现,其实很简单,就修改两个指针即可,下图是更详细的右旋过程解剖图:

《非线性数据结构 之 AVL树(1)》

上图是所谓的右旋转的操作详解,对于一棵发生了LL不平衡的BST树来说,只需要针对根节点进行右旋,就可以恢复平衡。这个右旋转的操作代码如下:

linktree right_rotate(linktree root)
{
	linktree p = root->lchild; // 使得 p 指向root的左孩子
	root->lchild = p->rchild; // 使得root的左孩子指针指向p的右孩子,上图1
	p->rchild = root; // 使得p的右孩子指针指向root,上图2
        // 下面是平衡因子(即树的高度)的重新计算
	root->bf = MAX(height(root->lchild), height(root->rchild)) + 1;
	p->bf = MAX(height(p->lchild), root->bf) + 1;

	return p;
}

对于上面的代码,第3,4,5行已经很明白了,第7,8行是平衡因子的重新计算,所谓的平衡因子就是一棵树的高度,我们将一棵树的高度(即深度)记录在其根节点中,那么当我们要衡量一棵树是否发生不平衡的时候就很方便了,只需要比较左右子树高度差是否小于等于1即可),那么一棵树的高度是多少呢?答案是:一棵树的高度,等于其左右子树的高度的最大值,再加1,(加上自己本身的高度)。

其中函数 height() 用来求一棵树的高度,代码如下:

int height(linktree root)
{
	return (root == NULL) ? 0 : root->bf; // 假如root是空树,那么高度为0,否则直接返回bf(bf就是树的高度)
}

而对于发生了RR不平衡的情况,对称地我们要进行左旋转,情况如下:

《非线性数据结构 之 AVL树(1)》

详细的左旋转步骤,跟右旋转是严格对称的:

《非线性数据结构 之 AVL树(1)》

其代码如下:

linktree left_rotate(linktree root)
{
	linktree p = root->rchild;
	root->rchild = p->lchild;
	p->lchild = root;

	root->bf = MAX(height(root->lchild), height(root->rchild)) + 1;
	p->bf = MAX(root->bf, height(p->rchild)) + 1;

	return p;
}

其实,旋转操作是二叉树的基本操作。弄懂了左旋转和右旋转之后,我们就可以很简单地解决所谓的LR不平衡和RL不平衡了,先来看LR不平衡,这种不平衡是由于插入的节点比左子树的根节点大而导致的,对于这种情况,我们如何恢复平衡呢? 请看下图:

《非线性数据结构 之 AVL树(1)》

对于LR不平衡,我们要进行两次旋转,第一次,从图2到图3,对发生LR不平衡的根节点的左子树节点进行左旋转,然后得到图4,其实图4的情况就是LL不平衡,此时根据上面的叙述,只需要对根节点进行右旋转即可,得到图5.   这时,红,白,黑三个节点的大小关系仍满足搜索树的特性,同时恢复了平衡。

其实现代码更简单,由于左旋转和右旋转已经实现了,这个操作其实就是左右旋转的结合而已:

linktree left_right_rotate(linktree root)
{
	root->lchild = left_rotate(root->lchild);
	return right_rotate(root);
}

完全对称地,所谓的RL不平衡,我们也要经过两次的旋转,将其重新平衡:

《非线性数据结构 之 AVL树(1)》
其代码如下:

linktree right_left_rotate(linktree root)
{
	root->rchild = right_rotate(root->rchild); // 对其右子树进行右旋转,上图2
	return left_rotate(root); // 对根节点进行左旋转,上图4
}

上面费了那么大劲儿讲完AVL树的基本操作:左旋,右旋,左右旋和右左旋之后,可以来讨论其《插入》操作了。

AVL树本身就是一棵二叉搜索树,所以其插入操作跟BST树是很类似的,具体的思路如下:

1,按照BST树的插入算法,插入一个节点。

2,插入节点之后,判断树的平衡是否遭到破坏。(判断左右子树高度差)

3,如果确实发生了不平衡,确定是哪一种(上面所述四种之一:LL不平衡,RR不平衡,LR不平衡,RL不平衡)

4,针对发生的不平衡,采用响应的旋转来恢复平衡。

下面是代码:

linktree AVL_insert(linktree root, tn_datatype data)
{
	// 以下是BST树的插入算法
	if(root == NULL)
		return new_node(data, NULL, NULL);

	if(data < root->data)
		root->lchild = AVL_insert(root->lchild, data);
	else if(data > root->data)
		root->rchild = AVL_insert(root->rchild, data);
	else
	{
		printf("%d is already exist.\n", data);
	}


	// 以下是有别于BST树的额外的操作
	if(height(root->lchild) - height(root->rchild) == 2) // 发生左不平衡,下面进一步确认不平衡类型
	{
		if(data < root->lchild->data) // LL不平衡,使用右旋转
			root = right_rotate(root);
		else if(data > root->lchild->data) //LR不平衡,使用左旋转
			root = left_rotate(root);
	}
	else if(height(root->rchild) - height(root->lchild) == 2)
	{
		if(data > root->rchild->data)
			root = left_rotate(root);
		else if(data < root->rchild->data)
			root = right_rotate(root);
	}

	root->bf = MAX(height(root->lchild), height(root->rchild)) + 1;
	return root;
}

AVL树的删除更加复杂一点,留到下一节介绍。

    原文作者:AVL树
    原文地址: https://blog.csdn.net/vincent040/article/details/8586962
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞