【学习笔记】AVL树的实现

此文章主要记录AVL树的insert和delete

其他操作与BST相似,见前面的平衡二叉树的实现

所有代码已经过编译,编译环境ubuntu12.04LTS GCC 4.6.3


————————————2014.3.24—————————————

昨天看完了AVL树的基本概念,对旋转的原理不太理解。

现在了解到两种结点结构体的定义,分别是


记录当前结点高度

typedef struct AVLNode
{
	int key;     //关键值
	int height;   //记录当前结点的高度
	struct AVLNode *leftChild;//左孩子
	struct AVLNode *rightChild;//右孩子
}AVLNode, *AVLTree;


记录左右孩子高度的差值

typedef struct AVLNode
{
	int key;    //关键值
	int bf;      //平衡因子,记录其左右树的高度之差
	struct AVLNode *leftChild;  //左孩子
	struct AVLNode *rightChild;  //右孩子
}AVLNode, *AVLTree;

啊啊啊啊啊啊!!!妈蛋今天还是没看懂怎么旋转的啊啊啊!!!抓狂

出去休息了会....


是这样 ,在比较之后发现第一种的代码效率要优于第二种(第一种插入删除结点之后需要递归更新树高,导致效率较低),所以决定使用使用第二种的结构实现。


————————————2014.3.25—————————————

今天早上搞懂了结点的插入操作,特别感谢参考资料中的《AVL树源代码_lray's blog》一文。(此文章后发现逻辑有问题,不推荐看。2013.4.10)


然后比较顺利的写出了三种遍历函数,但是在删除函数这里卡住了,因为发现上面提到的文章里面的delete函数存在问题,没有在删除结点之后对树进行再平衡。

这很蛋疼。

没办法继续查资料看其他人如何解决的,看了三四五六七八篇军没有结果,最终在百度百科找到了解决办法:在查找删除结点时使用一个栈,每走过一个结点,将其压入栈中。当找到并删除目标结点后,对刚才的栈进行出栈操作,每出一次栈,对出栈结点进行平衡调整,对,和插入时的平衡调整方案一样。于是这个问题就非常酷的被解决掉了。


————————————2014.3.26—————————————

C语言里不能使用引用........妈蛋= =......

改!

写完了...不报错了...终于可以愉快的./AVLTree了~

段!错!误!

卧槽!你大爷!!!劳资写了这么久你就告诉我段错误!!!段错误!!!你大爷!

OK,开始debug


————————————2014.4.10—————————————

前一阵准备面试,没有来得及debug和完善。

昨天完成了insert函数的实现,虽然之前看了几片文章帮助理解原理,但最终还是主要参考了严蔚敏的《数据结构》。

附上代码。

插入函数 代码

/*插入节点*/
Status InsertNode(AVLTree *node, ElemType key)
{
//	puts("into insert ok");//这是调试代码

	/*插入新的结点,树长高*/
	if((*node) == NULL)
	{
		(*node) = (AVLTree)malloc(sizeof(AVLNode));
		(*node)->leftChild	= NULL;
		(*node)->rightChild	= NULL;
		(*node)->key = key;
		(*node)->bf  = EH;

		taller = true;
	}

	/*若关键字小于当前结点*/
	else if(key < (*node)->key)
	{
		if(! InsertNode(&((*node)->leftChild), key) )
		  return false;//若插入失败返回false
		if(taller)
		{//已插入左子树且左子树长高
			switch((*node)->bf)
			{
				case LH://本身左高,现在左子树高再次+1,所以需要调整
					LeftBalance(node);	taller = false;	break;
				case EH://原本该结点平衡,现在左子树高+1,所以自身高度也+1(taller = true)
					(*node)->bf = LH;	taller = true;	break;
				case RH://原本右子树高,现在左子树高+1,所以平衡
					(*node)->bf = EH;	taller = false;	break;
			}
		}
	}

	/*若关键字大于当前结点*/
	else if(key > (*node)->key)
	{
		if(! InsertNode(&((*node)->rightChild), key) )
		  return false;//若插入失败返回false
		if(taller)
		{//已插入右子树且右子树长高
			switch((*node)->bf)
			{
				case LH://原本左子树高,现在右子树高+1,所以平衡
					(*node)->bf = EH;	taller = false;	break;
				case EH://原本该结点平衡,现在右子树高+1,所以自身高度也+1(taller = true)
					(*node)->bf = RH;	taller = true;	break;
				case RH://本身右高,现在右子树高再次+1,所以需要调整
					RightBalance(node);	taller = false;	break;
			}
		}
	}

	/*key值重复*/
	else
	{taller = false;	return false;	}

	return true;
}/*end of insert()*/


————————————2014.4.11—————————————

今天中午花了一个钟头把删除函数的逻辑理顺了下,可以将删除操作当作插入操作的逆过程,这样理解起来就方便多了。

然后将删除时的情况做了个分类,分别是:

1.待删key小于当前key

2.待删key大于当前key

3.待删key等于当前key时

    3.1.当前key存在右结点

    3.2.当前结点仅有左结点

    3.2.当前结点为叶子结点

以上几种情况中,3.2仅可能出现在树的倒数第二层,所以可以不做平衡因子的平衡检查。


附上代码:

删除函数 代码

/*删除结点*/
Status DeleteNode(AVLTree *node, ElemType key)
{
	/*待删结点小于当前结点*/
	if(key < (*node)->key)
	{
		DeleteNode( &((*node)->leftChild), key);
		if(shorter)//已删除结点,当左子树变短时,进行平衡判断
		{
			switch( (*node)->bf )
			{
				case LH://原来左高,现在左子树-1,所以等高
					(*node)->bf = EH;	shorter = true;
					break;
				case EH://原来等高,现在左子树-1,所以右高
					(*node)->bf = RH;	shorter = false;
					break;
				case RH://原来右高,现在左子树-1,所以需要左旋,调整平衡
					LeftBalance(node);	shorter = true;
					break;
			}
		}
	}

	/*待删结点大于当前结点*/
	else if(key > (*node)->key)
	{
		DeleteNode( &((*node)->rightChild), key);
		if(shorter)//已删除结点,当左子树变短时,进行平衡判断
		{
			switch( (*node)->bf )
			{
				case LH://原来左高,现在右子树-1,所以需要右旋,调整平衡
					LeftBalance(node);	shorter = true;
					break;
				case EH://原来等高,现在右子树-1,所以左高
					(*node)->bf = LH;	shorter = false;
					break;
				case RH://原来右高,现在右子树-1,所以等高
					(*node)->bf = EH;	shorter = true;
					break;
			}
		}
	}

	/*待删结点等于当前结点*/
	else
	{
		/*如果待删结点有右子树*/
		if( (*node)->rightChild )
		{
			(*node)->key = MinElement( (*node)->rightChild );
			DeleteNode( &((*node)->rightChild), (*node)->key );
			if(shorter)//已删除结点,当右子树变短时,进行平衡判断
			{
				switch( (*node)->bf )
				{
					case LH://原来左高,现在右子树-1,所以需要右旋,调整平衡
						LeftBalance(node);	shorter = true;
						break;
					case EH://原来等高,现在右子树-1,所以左高
						(*node)->bf = LH;	shorter = false;
						break;
					case RH://原来右高,现在右子树-1,所以等高
						(*node)->bf = EH;	shorter = true;
						break;
				}
			}
		}

		/*如果当前结点仅有左子树*/
		//那么该结点只可能存在于倒数第二层,所以无需判断删除结点后,当前结点是否失衡
		else if( (*node)->leftChild )
		{
			(*node)->key = (*node)->leftChild->key;
			(*node)->leftChild = NULL;
			free( (*node)->leftChild );
		}

		/*该结点为叶结点*/
		else
		{
			free(*node);
			*node = NULL;
			shorter = true;
		}
	}
}

到目前为止,历时19天,AVL树构建完成。(除insert和delete函数外的其他函数,见前文中《【学习笔记】平衡二叉树》中相关内容)


————————————2014.4.21—————————————

补充内容,发现之前居然忘记写旋转的东西了.......补上补上

旋转

AVL中的失衡有四种,分别是左左型,左右型,右左型和右右型。
平衡调节分别是:
    左左型:整体直接右旋
    左右型:先左孩子左旋再整体右旋
    右左型:先右孩子右旋再整体左旋
    右右型:整体直接左旋
可以看出,旋转归根到底只有两种:左旋和右旋。那么如何进行旋转?
简记:
右旋:孩子的右孩子给父亲当左孩子。
左旋:孩子的左孩子给父亲当右孩子。
下图是一个标准左左失衡的右旋操作(左旋为镜像对称)。


然后是左旋和右旋的代码。

右旋:

/*右旋:左树高旋转*/
void RightRotate(AVLTree *node)
{
	AVLTree lc = (*node)->leftChild;

	(*node)->leftChild = lc->rightChild;
	lc->rightChild = (*node);
	*node = lc;
}

左旋

/*左旋:右树高调整*/
void LeftRotate(AVLTree *node)
{
	AVLTree rc = (*node)->rightChild;

	(*node)->rightChild = rc->leftChild;
	rc->leftChild = (*node);
	*node = rc;
}

以及左右型和右左型失衡使用的左右平衡函数:

应用于左右型失衡的左平衡:

/*左平衡*/
void LeftBalance(AVLTree *node)
{
	AVLTree lc = (*node)->leftChild;//结点的左孩子
	AVLTree rd = lc->rightChild;	//结点的左孩子的右孩子

	switch(lc->bf)
	{
		case LH:
			(*node)->bf = lc->bf = EH;
			RightRotate(node);
			break;
		case RH:
			switch(rd->bf)
			{
				case LH:
					(*node)->bf = RH;	lc->bf = EH;
					break;
				case EH:
					(*node)->bf = lc->bf = EH;
					break;
				case RH:
					(*node)->bf = EH;	lc->bf = LH;
					break;
			}
			rd->bf = EH;
			LeftRotate(&((*node)->leftChild));//对该结点的左子树进行左旋
			RightRotate(node);//对该子树进行右旋
	}
}

应用于右左型失衡的右平衡:
/*右平衡*/
void RightBalance(AVLTree *node)
{
	AVLTree rc = (*node)->rightChild;//结点的右孩子
	AVLTree ld = rc->leftChild;	//结点的右孩子的左孩子

	switch(rc->bf)
	{
		case RH:
			(*node)->bf = rc->bf = EH;
			LeftRotate(node);
			break;
		case LH:
			switch(ld->bf)
			{
				case RH:
					(*node)->bf = LH;	rc->bf = EH;
					break;
				case EH:
					(*node)->bf = rc->bf = EH;
					break;
				case LH:
					(*node)->bf = EH;	rc->bf = RH;
					break;
			}
			ld->bf = EH;
			RightRotate(&((*node)->rightChild));//对该结点的右子树进行右旋
			LeftRotate(node);
	}

}

最后附上AVL树全部代码

AVL树整体代码

/* 
* Author:		xusongqi@live.com
* 
* Created Time: 2014年03月24日 星期一 21时36分31秒
* 
* FileName:     AVL_Tree.c
* 
* Description:  
*
* 
*
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define true 1
#define false 0
#define LH	 1
#define EH	 0
#define RH  -1
#define SIZE 10

typedef int Status;
typedef int ElemType;
_Bool	taller	= false;
_Bool	shorter	= false;

/*定义树的结构体*/
typedef struct AVLNode
{
	ElemType	key;			//关键字
	int			bf;				//结点的平衡度
	struct AVLNode *leftChild;	//左孩子
	struct AVLNode *rightChild;	//右孩子
}AVLNode,*AVLTree;

/*定义栈结构体*/
typedef struct Stack
{
	AVLTree node;//栈顶指针
	struct Stack *next;//指向下层
}Stack;

/*函数声明*/
Status InsertNode(AVLTree *node, ElemType key);	//插入结点
Status DeleteNode(AVLTree *node, ElemType key);	//删除结点
int	 MinElement(AVLTree node);					//寻找当前子树的最小关键字
void LeftBalance(AVLTree *node);				//左平衡
void RightBalance(AVLTree *node);				//右平衡
void LeftRotate(AVLTree *node);					//左旋
void RightRotate(AVLTree *node);				//右旋
Status PreOrder(AVLTree tree);					//先序输出AVL树
Status InOrder(AVLTree tree);					//中序输出AVL树
Status PostOrder(AVLTree tree);					//后序输出AVL树

//void CreateStack(Stack *stack);					//初始化栈
//void PushStack(Stack *stack, AVLTree node);		//入栈
//Stack *PopStack(Stack *stack);					//出栈

//插入节点
Status InsertNode(AVLTree *node, ElemType key)
{
//	puts("into insert ok");//这是调试代码

	/*插入新的结点,树长高*/
	if((*node) == NULL)
	{
		(*node) = (AVLTree)malloc(sizeof(AVLNode));
		(*node)->leftChild	= NULL;
		(*node)->rightChild	= NULL;
		(*node)->key = key;
		(*node)->bf  = EH;

		taller = true;
	}

	/*若关键字小于当前结点*/
	else if(key < (*node)->key)
	{
		if(! InsertNode(&((*node)->leftChild), key) )
		  return false;//若插入失败返回false
		if(taller)
		{//已插入左子树且左子树长高
			switch((*node)->bf)
			{
				case LH://本身左高,现在左子树高再次+1,所以需要调整
					LeftBalance(node);	taller = false;	break;
				case EH://原本该结点平衡,现在左子树高+1,所以自身高度也+1(taller = true)
					(*node)->bf = LH;	taller = true;	break;
				case RH://原本右子树高,现在左子树高+1,所以平衡
					(*node)->bf = EH;	taller = false;	break;
			}
		}
	}

	/*若关键字大于当前结点*/
	else if(key > (*node)->key)
	{
		if(! InsertNode(&((*node)->rightChild), key) )
		  return false;//若插入失败返回false
		if(taller)
		{//已插入右子树且右子树长高
			switch((*node)->bf)
			{
				case LH://原本左子树高,现在右子树高+1,所以平衡
					(*node)->bf = EH;	taller = false;	break;
				case EH://原本该结点平衡,现在右子树高+1,所以自身高度也+1(taller = true)
					(*node)->bf = RH;	taller = true;	break;
				case RH://本身右高,现在右子树高再次+1,所以需要调整
					RightBalance(node);	taller = false;	break;
			}
		}
	}

	/*key值重复*/
	else
	{taller = false;	return false;	}

	return true;
}/*end of insert()*/

/*删除结点*/
Status DeleteNode(AVLTree *node, ElemType key)
{
	/*待删结点小于当前结点*/
	if(key < (*node)->key)
	{
		DeleteNode( &((*node)->leftChild), key);
		if(shorter)//已删除结点,当左子树变短时,进行平衡判断
		{
			switch( (*node)->bf )
			{
				case LH://原来左高,现在左子树-1,所以等高
					(*node)->bf = EH;	shorter = true;
					break;
				case EH://原来等高,现在左子树-1,所以右高
					(*node)->bf = RH;	shorter = false;
					break;
				case RH://原来右高,现在左子树-1,所以需要左旋,调整平衡
					LeftBalance(node);	shorter = true;
					break;
			}
		}
	}

	/*待删结点大于当前结点*/
	else if(key > (*node)->key)
	{
		DeleteNode( &((*node)->rightChild), key);
		if(shorter)//已删除结点,当左子树变短时,进行平衡判断
		{
			switch( (*node)->bf )
			{
				case LH://原来左高,现在右子树-1,所以需要右旋,调整平衡
					LeftBalance(node);	shorter = true;
					break;
				case EH://原来等高,现在右子树-1,所以左高
					(*node)->bf = LH;	shorter = false;
					break;
				case RH://原来右高,现在右子树-1,所以等高
					(*node)->bf = EH;	shorter = true;
					break;
			}
		}
	}

	/*待删结点等于当前结点*/
	else
	{
		/*如果待删结点有右子树*/
		if( (*node)->rightChild )
		{
			(*node)->key = MinElement( (*node)->rightChild );
			DeleteNode( &((*node)->rightChild), (*node)->key );
			if(shorter)//已删除结点,当右子树变短时,进行平衡判断
			{
				switch( (*node)->bf )
				{
					case LH://原来左高,现在右子树-1,所以需要右旋,调整平衡
						LeftBalance(node);	shorter = true;
						break;
					case EH://原来等高,现在右子树-1,所以左高
						(*node)->bf = LH;	shorter = false;
						break;
					case RH://原来右高,现在右子树-1,所以等高
						(*node)->bf = EH;	shorter = true;
						break;
				}
			}
		}

		/*如果当前结点仅有左子树*/
		//那么该结点只可能存在于倒数第二层,所以无需判断删除结点后,当前结点是否失衡
		else if( (*node)->leftChild )
		{
			(*node)->key = (*node)->leftChild->key;
			(*node)->leftChild = NULL;
			free( (*node)->leftChild );
		}

		/*该结点为叶结点*/
		else
		{
			free(*node);
			*node = NULL;
			shorter = true;
		}
	}
}

/*寻找最小元素*/
int MinElement(AVLTree node)
{
	if( node->leftChild )
	  return MinElement(node->leftChild);
	else 
	  return node->key;
}

/*左平衡*/
void LeftBalance(AVLTree *node)
{
	AVLTree lc = (*node)->leftChild;//结点的左孩子
	AVLTree rd = lc->rightChild;	//结点的左孩子的右孩子

	switch(lc->bf)
	{
		case LH:
			(*node)->bf = lc->bf = EH;
			RightRotate(node);
			break;
		case RH:
			switch(rd->bf)
			{
				case LH:
					(*node)->bf = RH;	lc->bf = EH;
					break;
				case EH:
					(*node)->bf = lc->bf = EH;
					break;
				case RH:
					(*node)->bf = EH;	lc->bf = LH;
					break;
			}
			rd->bf = EH;
			LeftRotate(&((*node)->leftChild));//对该结点的左子树进行左旋
			RightRotate(node);//对该子树进行右旋
	}
}

/*右平衡*/
void RightBalance(AVLTree *node)
{
	AVLTree rc = (*node)->rightChild;//结点的右孩子
	AVLTree ld = rc->leftChild;	//结点的右孩子的左孩子

	switch(rc->bf)
	{
		case RH:
			(*node)->bf = rc->bf = EH;
			LeftRotate(node);
			break;
		case LH:
			switch(ld->bf)
			{
				case RH:
					(*node)->bf = LH;	rc->bf = EH;
					break;
				case EH:
					(*node)->bf = rc->bf = EH;
					break;
				case LH:
					(*node)->bf = EH;	rc->bf = RH;
					break;
			}
			ld->bf = EH;
			RightRotate(&((*node)->rightChild));//对该结点的右子树进行右旋
			LeftRotate(node);
	}

}

/*右旋:左树高旋转*/
void RightRotate(AVLTree *node)
{
	AVLTree lc = (*node)->leftChild;

	(*node)->leftChild = lc->rightChild;
	lc->rightChild = (*node);
	*node = lc;
}

/*左旋:右树高调整*/
void LeftRotate(AVLTree *node)
{
	AVLTree rc = (*node)->rightChild;

	(*node)->rightChild = rc->leftChild;
	rc->leftChild = (*node);
	*node = rc;
}

/*先序输出*/
Status PreOrder(AVLTree tree)
{
	if(tree != NULL)
	{
		printf("%d ",tree->key);
		PreOrder(tree->leftChild);
		PreOrder(tree->rightChild);
	}
}

/*中序输出*/
Status InOrder(AVLTree tree)
{
	if(tree != NULL)
	{
		InOrder(tree->leftChild);
		printf("%d ",tree->key);
		InOrder(tree->rightChild);
	}
}

/*后序输出*/
Status PostOrder(AVLTree tree)
{
	if(tree != NULL)
	{
		PostOrder(tree->leftChild);
		PostOrder(tree->rightChild);
		printf("%d ",tree->key);
	}
}

int main(void)
{
	int array[SIZE] = {3,5,4,6,9,1,8,10,7,2};
//	int array[SIZE] = {1,2,3,4,5,6,7,8,9,10};
	int choose;
	int del;
	int i;
	AVLTree root = NULL;

	//插入数组中的元素
	for(i = 0; i < SIZE; i++)
	{ 
		//puts("into for() ok");//这是调试代码
		InsertNode(&root, array[i]);
		puts("中序");
		InOrder(root);//调试语句
		puts("\n先序");
		PreOrder(root);
		puts("\n后序");
		PostOrder(root);
		puts("");
	}
	while(1)
	{
		printf("1.exit\n");
		printf("2.delete\n");
		printf("3.preorder\n");
		printf("4.inorder\n");
		printf("5.postorder\n");
		scanf("%d",&choose);

		switch(choose)
		{
			case 1:exit(0);
				   break;
			case 2:printf("which?\n");
				   scanf("%d",&del);
				   DeleteNode(&root,del);
				   break;
			case 3:PreOrder(root);
				   puts("");
				   break;
			case 4:InOrder(root);
				   puts("");
				   break;
			case 5:PostOrder(root);
				   puts("");
				   break;
			default:puts("WRONG CHOOSE\n\n");
					sleep(1);
		}
	}
}


参考资料:

《数据结构》 严蔚敏 平衡二叉树章节

AVL树_百度百科

AVL树源代码_lray's blog 注:此文章逻辑细节有问题,不建议看

条理清晰的学习红黑树(插入节点)—骥在路上

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值