【数据结构】AVL树

一、概念

AVL树:高度平衡的二叉搜索树,共有继承了BST树,有BST树的全部特性。
AVL树的左孩子、右孩子树都是AVL树,并且左子树,右子树的高度差(右子树高度 - 左子树高度)不超过1 

高度差:结点的平衡因子。如图,该树各个节点的平衡因子,有超过1,所以这不是一个AVL树。

二、AVL树的作用:

对于一般的二叉搜索树(Binary Search Tree),其期望高度(即为一棵平衡树时)为log2n,其各操作的时间复杂度(O(log2n))同时也由此而决定。但是,在某些极端的情况下(如在插入的序列是有序的时),二叉搜索树将退化成近似链或链,此时,其操作的时间复杂度将退化成线性的,即O(n)。我们可以通过随机化建立二叉搜索树来尽量的避免这种情况,但是在进行了多次的操作之后,由于在删除时,我们总是选择将待删除节点的后继代替它本身,这样就会造成总是右边的节点数目减少,以至于树向左偏沉。这同时也会造成树的平衡性受到破坏,提高它的操作的时间复杂度。

二、分析及实现

1.定义:
 

typedef int KeyType;//关键码
typedef struct AVLNode    //树节点:设计时带有头结点:
{
	AVLNode* leftchild; //左孩子
	AVLNode* prarent;          //双亲指针 BST、AVL、RB中都有双亲指针,因为需要回溯
	AVLNode* rightchild;//右孩子
	int balance;    //平衡因子:-1 0 1
	KeyType key;  //值
}AVLNode;

typedef struct //AVL 树 头节点:有头结点则不需要二级指针来改变指针的指向(左旋右旋)
{
	AVLNode* head;  //树的头结点
	int cursize;  //结点个数
}AVLTree;
AVLNode* Buynode(AVLNode* parent=NULL) 
{
	AVLNode* s = (AVLNode*)malloc(sizeof(AVLNode));
	if(s == NULL) exit(1);
	memset(s,0,sizeof(*s));
	s->prarent = parent;
	return s;
}
void Freenode(AVLNode* p)
{
	free(p);
}

void InitAVTree(AVLTree& myt) //初始化一棵树
{
	myt.head = Buynode();
	myt.cursize = 0;
}

2.插入,单旋转:

在AVL树中,插入时,造成不平衡,所以需要:旋转(左旋、右旋)

左旋:右边的数据多,造成不平衡了,所以就左旋。如图要插入120.造成了树的不平衡。45节点的balance为2.

右旋:树左边的几点多,造成不平衡,所以就右旋一下,将左边的结点向右旋转过来。

 

 进行左旋:

函数:

void RotateLeft(AVLNode*head,AVLNode*ptr)//ptr要旋转的结点 head头结点

 ptr指向45结点,head为树的头结点。

分3步骤:

第一步:当前树(45为小树根结点的这颗树)的根节点重新设定,设定为45结点的右孩子。即不平衡结点的右孩子设定为新的根节点。

第二步:根节点的右孩子要 指向 新根的左孩子

第三步:新根的左孩子指针 指向原来的根

 三句代码:

 

//左旋
void RotateLeft(AVLNode*head,AVLNode*ptr) //3个指向改变 3个双亲改变:ptr要旋转的结点 head头结点
{
	AVLNode* newroot = ptr->rightchild;
	ptr->rightchild = newroot->leftchild;
	newroot->leftchild = ptr;

	//下面if else解决ptr是否为树的根节点问题
	if(ptr->prarent == head) //ptr的双亲 是 树的头结点,即ptr为树的根节点
	{
		head = newroot;//那么头结点 要指向新的根
	}
	else//ptr的双亲 不是 树的头结点,即ptr不是树的根节点
	{
		if(ptr->prarent->leftchild == ptr) //ptr是其双亲的左孩子
		{
			ptr->prarent->leftchild = newroot;//新的根
		}
		else        //ptr是其双亲的右孩子
		{
			ptr->prarent->rightchild = newroot;
		}
	}
}
​

但是parent的指向还未改变,所以还需要对parent指针域改变。如图中紫色圆圈标记,45,78,89 双亲改变。

 

接下来解决newroot的双亲改变,原来的根(即图中的45结点)的双亲改变,newroot的左孩子(图中78结点)的双亲改变。

紫色箭头为指向双亲。

 要注意一个特殊情况:newroot的左孩子为空

//左旋
void RotateLeft(AVLNode*head,AVLNode*ptr) //3个指向改变 3个双亲改变:ptr要旋转的结点 head头结点
{
	AVLNode* newroot = ptr->rightchild;//第一个指向
	newroot->prarent = ptr->prarent; //1.newroot的双亲改变
	ptr->rightchild = newroot->leftchild;
	if(newroot->leftchild != NULL)
	{
		newroot->leftchild->prarent = ptr; //2.改变newroot的左孩子的双亲
	}
	newroot->leftchild = ptr;//2.第二个指向

	//下面if else解决ptr是否为树的根节点问题
	if(ptr->prarent == head) //ptr的双亲 是 树的头结点,即ptr为树的根节点
	{
		head = newroot;//那么头结点 要指向新的根
	}
	else//ptr的双亲 不是 树的头结点,即ptr不是树的根节点
	{
		if(ptr->prarent->leftchild == ptr) //ptr是其双亲的左孩子
		{
			ptr->prarent->leftchild = newroot;//新的根
		}
		else        //ptr是其双亲的右孩子
		{
			ptr->prarent->rightchild = newroot;
		}
	}
	ptr->prarent = newroot;//3.ptr的双亲 指向 newroot
}

右旋:和左旋同理

//右旋
void RotateRight(AVLNode*head,AVLNode*ptr) 
{
	AVLNode* newroot = ptr->leftchild;//1.新的结点
	newroot->prarent = ptr->prarent;	
	ptr->leftchild = newroot->rightchild;//
	if(newroot->rightchild != NULL)
	{
		newroot->rightchild->prarent = ptr;//newroot 左孩子双亲改变
	}
	newroot->rightchild = ptr;//3.第三个
	if(ptr->prarent == head)
	{
		head->prarent = newroot;
	}
	else
	{
		if(ptr->prarent->leftchild == ptr)
		{
			ptr->prarent->leftchild = newroot;
		}
		else
		{
			ptr->prarent->rightchild = newroot;
		}
	}
	ptr->prarent = newroot;//3,双亲
}

3.双旋转:两种:先左后右,先右后左 

先看一个简单例子:什么情况下要双旋转:

 

 

void LeftBalance(AVLTree& myt,AVLNode* ptr)//左平衡
{
	AVLNode* rightsub = NULL;
	AVLNode* leftsub = ptr->rightchild;
	switch(leftsub->balance)
	{
	case 0:cout<<" right already balance"<<endl;break;
	case -1:
		ptr->balance = 0;
		leftsub->balance = 0;
		RotateRight(myt.head,ptr);
		break;
	case 1:
		rightsub = leftsub->rightchild;
		switch(rightsub->balance)
		{
		case 0:break;
		case 1:break;
		case -1:break;
		}
		RotateLeft(myt.head,leftsub);
		RotateRight(myt.head,ptr);
		break;
	}
}

void RightBalance(AVLTree& myt,AVLNode* ptr)//右平衡
{
	AVLNode* rightsub = ptr->rightchild;//右孩子结点
	AVLNode* leftsub = NULL;//左孩子结点
	switch(rightsub->balance)
	{
	case 0:cout<<" right already balance"<<endl;break;
	case 1:
		ptr->balance = 0;
		rightsub->balance = 0;
		RotateLeft(myt.head,ptr);
		break;
	case -1:
		leftsub = rightsub->leftchild;
		switch(leftsub->balance)
		{
		case 0:break;
		case 1:
			ptr->balance = -1;
			rightsub->balance = 0;
			break;
		case -1:
			ptr->balance = 0;
			rightsub->balance = 1;
			break;
		}
		leftsub->balance = 0;
		RotateRight(myt.head,rightsub);
		RotateLeft(myt.head,ptr);
		break;
	}
}
void AdjustTree(AVLTree& myt,AVLNode* ptr)//调整函数
{
	AVLNode* pa = ptr->prarent;
	for(;;)//条件退出
	{
		if(pa->leftchild == ptr)
		{
			switch(pa->balance)
			{
			case 0:pa->balance = -1;break;
			case 1:pa->balance = 0;break;
			case -1:LeftBalance(myt,pa);
				break;
			}
		}
		else
		{
			switch(pa->balance)
			{
			case 0:pa->balance = 1;break;
			case -1:pa->balance = 0;break;
			case 1:RightBalance(myt,pa);
				break;
			}
		}
		ptr = pa;
		pa = pa->prarent;
	}
}

bool InsertItem(AVLTree& myt,KeyType kx)
{
	//1.找到位置
	AVLNode* pa = myt.head;          //head
	AVLNode* p = myt.head->prarent;//root
	while(p != NULL && p->key != kx)
	{
		pa = p;
		p = kx<p->key ? p->leftchild : p->rightchild;
	}
	if(p != NULL && p->key == kx) return false;//找到相同的 

	p = Buynode(pa);
	if(pa == myt.head)
	{
		myt.head->prarent = p;
		myt.head->leftchild = p;
		myt.head->rightchild = p;
	}
	else
	{
		if(p->key < pa->key)
		{
			pa->leftchild = p;
			if(p->key < myt.head->leftchild->key)//维护head结点中最小
			{
				myt.head->leftchild = p;
			}
		}
		else
		{
			pa->rightchild = p;
			if(p->key > myt.head->rightchild->key)//维护head结点中最大
			{
				myt.head->rightchild = p;
			}
		}
		AdjustTree(myt,p);//调整函数
	}
	myt.cursize += 1;
	return true;
}

 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值