AVL树的实现

目录

一、AVL树的概念

二、AVL树的构建

1、节点的定义

2、AVL树的插入操作

3、旋转的四种情况及处理方式

模型Ⅰ、根节点的平衡因子为2,插入节点的子树的平衡因子为1

 模型Ⅱ、根节点的平衡因子为-2,插入节点的子树的平衡因子为-1

模型Ⅲ、根节点的平衡因子为2,插入节点的子树的平衡因子为-1

模型Ⅳ、根节点的平衡因子为-2,插入节点的子树的平衡因子为1

三、AVL树的验证

四、AVL树的删除

一、AVL树的概念

AVL树是一种高度平衡的二叉搜索树,由俄罗斯的两位数学家G.M.Adelson-Velskii 和E.M.Landis在1962年提出,用于解决二叉搜索树在极端场景下左、右严重不平衡而导致效率低下的问题。


满足以下条件的二叉树属于AVL树

1、二叉搜索树

2、左、右子树高度差的绝对值不超过1(高度平衡)

3、左、右子树也是AVL树

二、AVL树的构建

AVL树的构建原理和二叉搜索树大致相同,唯一的不同点及困难点在于:

如何在频繁的插入/删除操作中始终维持二叉搜索树的高度平衡?

因此,本篇文章重点探讨如何实现二叉搜索树的高度平衡,不会着重讲解或验证二叉搜索树的特性,因此需要大家先掌握二叉搜索树的相关知识。

1、节点的定义

与常规二叉搜索树不同的是,我们要在节点中新增一个平衡因子,用于维护左、右子树高度平衡;

另外,还要增加一个指向父节点的指针,方便我们向上层遍历。

节点的定义如下:

//二叉树节点
template <class k,class v>
struct TreeNode
{
	TreeNode(const pair<k, v>& kv)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _fb(0)
	{

	}
	pair<k, v> _kv;//kv结构
	TreeNode<k, v>* _left;
	TreeNode<k, v>* _right;
	TreeNode<k, v>* _parent;
	int _fb;//平衡因子(右子树高度-左子树高度)
};

:这里用右子树高度 - 左子树高度来表示平衡因子,其实用左子树高度 - 右子树高度也是一样的道理。

结合AVL树的特性,得出以下结论:

1、任意一个叶子节点的平衡因子都是0

2、右子树高度变大或左子树高度变小,平衡因子都会变大;左子树高度变大或右子树高度变小,平衡因子都会变小;

3、正常情况下,平衡因子的值只能是-1、0、1三种情况,当平衡因子变成-2或2时,说明其左、右子树不再平衡,需要对结构进行调整

2、AVL树的插入操作

我们通过插入节点来构建AVL树。

首先,通过father和cur两个指针的迭代先找到合适的插入位置。其中father表示要插入的根节点,cur表示插入位置。当cur走到nullptr时,抵达最终的插入位置,创建新节点并赋值给cur,然后将father和cur进行链接。

查找插入位置的代码逻辑如下:

        if (_root == nullptr)//当前为空树
		{
			_root = new TreeNode(kv);
			return true;
		}
		//当前不是空树,先查找插入位置
		TreeNode* father = nullptr, * cur = _root;
		while (cur)
		{
			if (cur->_kv.first < kv.first)
			{
				father = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				father = cur;
				cur = cur->_left;
			}
			else//节点已存在
				return false;
		}
		//cur为空,找到了插入位置.插入节点
		cur = new TreeNode(kv);
		cur->_parent = father;
		if (father->_kv.first < cur->_kv.first)
		{
			father->_right = cur;
		}
		else
		{
			father->_left = cur;
		}

由于cur由之前的空节点变成了叶子节点,则对于father而言,cur这颗子树的高度由0变成了1。其中一棵子树的高度增加了1,father的平衡因子必然要加一或减一。如果cur是father的右子树,则father->_bf++;如果cur是father的左子树,则father->_bf--;

father的平衡因子调整以后有以下三种情况:

1、father->_bf==0。这就意味着之前father->_bf一定是1或者-1。此时的情景是:

新节点插入到了之前较矮的子树中,使得较矮子树的高度增加了1,最后两棵子树的高度变得相同,而father整棵树的高度不变。因此不会再影响father的上级根节点。

2、father->_bf==1或-1。这就意味着之前father->_bf一定是0。此时的情景是:

新节点插入到了原来高度相同的左、右子树的其中一棵,使得其中一棵子树的高度增加了1。同时,也使得father整棵树的高度也增加了1。

由于father本身也是其上级父节点的子树,其高度的增加势必也会影响其上级父节点的平衡因子,因此,需要不断往上迭代处理上级根节点的平衡因子。直到根节点的平衡因子变成0时完成调整。

3、father->_bf==2或 -2。这就意味着,之前father->_bf一定是1或-1;此时的情景是:

新节点插入到原本高度差为1的较高的那棵子树中,使得左、右子树的高度差变成2。

此时,左、右子树的高度变得不再平衡,需要调整father这棵树的结构,使其变得平衡的同时,令其恢复到插入前的高度,从而避免再向上层去调整平衡因子。

调整平衡因子的代码逻辑如下:

while (father)//father为空表示走到根节点
{
	//调整平衡因子
	if (father->_kv.first < cur->_kv.first)//father的右子树高度增加了
	{
		father->_fb++;
	}
	else//father的左子树高度增加了
	{
		father->_fb--;
	}
	//查看调整后的father的平衡因子
	if (father->_fb == 0)//左右子树高度相同,则必然之前高度较低的子树被补平了,
	{                    //father的高度不变
		braek;//结束调整
	}
	else if (father->_fb == 1 || father->_fb == -1)
	{   //之前father的平衡因子一定为0,此时father的高度一定增加了
		//而father作为爷爷节点的子树,其高度增加了必然影响爷爷节点的平衡因子
		cur = father;
		father = father->father;//往上层迭代
	}
	else if (father->_fb == 2 || father->_fb == -2)//高度变得不平衡,对结构进行调整
	{
		//进行旋转
	}
	else//未知异常
	{
		assert(false);//报错
	}
}

接下来,我们重点分析高度不平衡时如何对子树的结构进行调整,使其恢复平衡。通常,我们将这个调整的操作称为旋转。

3、旋转的四种情况及处理方式

首先,我们先明确旋转需要达到怎样的效果。

旋转需要达到的效果有以下几点:

a、继续维持二叉搜索树的结构

b、使左、右子树的高度变得平衡

c、调整旋转后节点的平衡因子

d、根节点对应的子树恢复到插入之前的高度(不用再调整上层的平衡因子)

其次,我们可以通过根节点的平衡因子来推导旋转的结构模型。

当一个根节点的平衡因子由1变成2或由-1变成-2时,插入节点的那棵子树的高度必然发生了变化。

因此插入节点的子树的平衡因子一定是1或-1。

(如果对应子树的平衡因子为0,则当前根节点的平衡因子不会发生变化;如果对应子树的平衡因子为2或-2,则会先完成当前子树的旋转,当前根节点的平衡因子同样不会发生变化)。

由此又可以推导出对应子树在插入节点之前,其平衡因子一定为0。也就是说,对应子树的左、右两边在插入节点之前高度一定相同。

因此就有4种不同的组合情况,分别为:

Ⅰ:根节点的平衡因子为2,插入节点的子树的平衡因子为1

Ⅱ:根节点的平衡因子为-2,插入节点的子树的平衡因子为-1

Ⅲ:根节点的平衡因子为2,插入节点的子树的平衡因子为-1

Ⅳ:根节点的平衡因子为-2,插入节点的子树的平衡因子为1

接下来,带着以上四个目标分别对四种不同的旋转模型进行分析和处理。

模型Ⅰ、根节点的平衡因子为2,插入节点的子树的平衡因子为1

节点插入前、后的结构模型如下图:

此时右边高于左边,我们采取将根节点及左子树下压的方式进行调整,这个操作称为左旋。

具体操作如下:

① 将cur的左子树b链接到father的右子树(father<b<cur)

② 将father链接到cur的左子树

③将cur取代father变成这棵子树新的根节点,并将其链接到father曾经的父节点下面

旋转过程及最终模型如下图:

 

 观察旋转后的模型结构,依旧是二叉搜索树的结构,且左右两边高度平衡,同时旋转后整棵子树的高度和插入前一样,都是h+2。旋转的四个目标已经完成了3个,接下来只需要调整节点的平衡因子即可。

在上述旋转的过程中,只有father和cur的平衡因子会发生改变,且旋转后father和cur的左、右子树高度都相同,因此只需将father和cur的平衡因子设为0即可。

插入前、插入后、旋转后的结构模型对比如下图:

 左旋的代码逻辑如下:

    //左旋
	void RotateL(TreeNode* father)
	{
		assert(father);//根节点不能为空
		TreeNode* grandpa = father->_parent;//当前根节点的父节点
		TreeNode* rightTree = father->_right;//右子树
		TreeNode* rightTree_left = rightTree->_left;//右子树的左子树
		//先将右子树的左子树链接到根节点右边
		father->_right = rightTree_left;
		if (rightTree_left != nullptr)
			rightTree_left->_parent = father;
		//然后将father链接到rightTree的左边
		rightTree->_left = father;
		father->_parent = rightTree;
		//接着将grandpa与rightTree进行链接
		rightTree->_parent = grandpa;
		if (grandpa == nullptr)//当前parent就是根节点
		{
			_root = rightTree;
		}
		else//grandpa不为空
		{
			if (grandpa->_left == father)
			{
				grandpa->_left = rightTree;
			}
			else
			{
				grandpa->_right = rightTree;
			}
		}
		//最后将father和rightTree的平衡因子改成0
		father->_fb = rightTree->_fb = 0;
	}

 模型Ⅱ、根节点的平衡因子为-2,插入节点的子树的平衡因子为-1

节点插入前、后的结构模型如下图:

此时左边高于右边,我们采取将根节点及右子树下压的方式进行调整,这个操作称为右旋。

具体操作如下:

① 将cur的右子树b链接到father的左子树(cur<b<father)

② 将father链接到cur的右子树

③将cur取代father变成这棵子树新的根节点,并将其链接到father曾经的父节点下面

旋转过程及最终模型如下图:

 

观察旋转后的模型结构,依旧是二叉搜索树的结构,且左右两边高度平衡,同时旋转后整棵子树的高度和插入前一样,都是h+2。旋转的四个目标已经完成了3个,接下来只需要调整节点的平衡因子即可。

在上述旋转的过程中,只有father和cur的平衡因子会发生改变,且旋转后father和cur的左、右子树高度都相同,因此只需将father和cur的平衡因子设为0即可。

插入前、插入后、旋转后的结构模型对比如下图:

 右旋的代码逻辑如下:

    //右旋
	void RotateR(TreeNode* father)
	{
		assert(father);//根节点不能为空
		TreeNode* grandpa = father->_parent;//father的父节点
		TreeNode* leftTree = father->_left;//左子树
		TreeNode* leftTree_right = leftTree->_right;//左子树的右子树
		//先将左子树的右子树链接到father左边
		father->_left = leftTree_right;
		if (leftTree_right)
			leftTree_right->_parent = father;
		//然后将father链接到左子树的右边
		leftTree->_right = father;
		father->_parent = leftTree;
		//最后将leftTree与grandpa进行链接
		leftTree->_parent = grandpa;
		if (grandpa == nullptr)
		{
			_root = leftTree;
		}
		else
		{
			if (grandpa->_left == father)
			{
				grandpa->_left = leftTree;
			}
			else
			{
				grandpa->_right = leftTree;
			}
		}
		//最后将father和leftTree的平衡因子调整为0
		father->_fb = leftTree->_fb = 0;
	}

模型Ⅲ、根节点的平衡因子为2,插入节点的子树的平衡因子为-1

节点插入前、后的结构模型如下图:

通过b的高度增加了1,可以推导出节点插入后,b的平衡因子一定是 1或-1。由此又可以推导出,在节点插入前,b的平衡因子一定是0。

 将b部分划分成根+左右子树的形态,则结构模型转化成以下形态:

 

 观察插入后的模型,发现具有以下特点:

1、father<cur_L<cur。单从节点值而言,cur_L可以作为father和cur的根节点,father和cur可以分别作为cur_L的左、右子树。如果按照这种方式链接,我们需要对cur_L原来的左子树b和原来的右子树c进行处理。

2、father<b<cur_L,cur_L<c<cur。从大小关系上看,b正好可以作为father的右子树,c正好可以作为cur的左子树。

我们尝试按照上述方式对father整棵子树进行重组,得到以下模型:

观察重组后的模型结构:

如果插入位置是b,则father的左子树高度为h,右子树高度为也是h,father满足AVL树的条件,且father的高度为h+1;而cur的左子树高度为h-1,右子树高度为h,cur满足AVL树的条件,且cur整棵树的高度为h+1。而father和cur分别作为cur_L的左、右子树,其高度都是h+1,则cur_L整棵树满足AVL树的条件,且cur_L的高度为h+2,与节点插入前整棵子树的高度相同。

如果插入位置是c,则father的左子树高度为h,右子树高度为h-1,father满足AVL树的条件,且father的高度为h+1;而cur的左子树高度为h,右子树高度也是h,cur满足AVL树的条件,且cur整棵树的高度为h+1。而father和cur分别作为cur_L的左、右子树,其高度都是h+1,则cur_L整棵树满足AVL树的条件且cur_L的高度为h+2,与节点插入前整棵子树的高度相同。

也就是说,不管插入位置是b还是c,按照以上方式对整棵子树进行重组,都可以形成一个与插入前高度相同的AVL树。

我们将上述整棵子树结构重组的操作拆分成两个步骤来执行:

第一步:将cur_L的右子树c链接到cur的左子树,再将cur链接到cur_L的右子树

我们发现这个步骤和右旋的操作基本相同,唯一的区别在于没有将cur_L链接到cur原来的父节点下面。在此回顾一下右旋的操作:

① 将cur的右子树b链接到father的左子树(cur<b<father)

② 将father链接到cur的右子树

③将cur取代father变成这棵子树新的根节点,并将其链接到father曾经的父节点下面

分别对cur子树进行步骤一操作和右旋操作,分别得到以下模型:

 第二步:将cur_L的左子树b链接到father的右子树,将father链接到cur_L的左子树,然后cur_L取代father变成这棵子树新的根节点,将其链接到father曾经的父节点下面。

我们发现这个步骤和左旋的操作完全相同,在此回顾一下左旋的操作:

① 将cur的左子树b链接到father的右子树(father<b<cur)

② 将father链接到cur的左子树

③将cur取代father变成这棵子树新的根节点,并将其链接到father曾经的父节点下面

分别对father进行步骤一操作和右旋操作,得到以下模型:

 根据模型结构我们发现,按照步骤一加步骤二的方式进行重组,和先对cur进行右旋,再对father进行左旋的方式重组,最终得到的模型结构都是一样的。

因此,我们可以直接复用之前的代码逻辑进行结构调整,将该操作称为右左双旋。

最后只需要调整father、cur、cur_L的平衡因子即可。

再次回到我们之前对重组后结构的分析:

如果插入位置是b,则father的左子树高度为h,右子树高度为也是h,father满足AVL树的条件,且father的高度为h+1;而cur的左子树高度为h-1,右子树高度为h,cur满足AVL树的条件,且cur整棵树的高度为h+1。而father和cur分别作为cur_L的左、右子树,其高度都是h+1,则cur_L整棵树满足AVL树的条件,且cur_L的高度为h+2,与节点插入前整棵子树的高度相同。

如果插入位置是c,则father的左子树高度为h,右子树高度为h-1,father满足AVL树的条件,且father的高度为h+1;而cur的左子树高度为h,右子树高度也是h,cur满足AVL树的条件,且cur整棵树的高度为h+1。而father和cur分别作为cur_L的左、右子树,其高度都是h+1,则cur_L整棵树满足AVL树的条件且cur_L的高度为h+2,与节点插入前整棵子树的高度相同。

如果插入位置原来是空树,则h==0。father的左、右子树高度都为0,father满足AVL树的条件,且father的高度为1;而cur的左、右子树高度也是0,cur满足AVL树的条件,且cur整棵树的高度为1。而father和cur分别作为cur_L的左、右子树,其高度都是1,则cur_L整棵树满足AVL树的条件且cur_L的高度为2,与节点插入前整棵子树的高度相同。

由此得出结论:cur_L的平衡因子一定为0。father和cur的平衡因子与节点的插入位置有关,而不同的插入位置又会影响节点插入后cur_L的平衡因子,因此,father和cur的平衡因子可以通过节点插入后cur_L的平衡因子来确定。

当插入的子树本身是空树时,h==0。进行旋转前cur_L的平衡因子为0。旋转后cur_L、father、cur的平衡因子都是0。

当插入位置为cur_L的左子树b时,进行旋转前cur_L的平衡因子为-1。旋转后cur_L的平衡因子为0,father的平衡因子也是0,而cur的平衡因子为1。

当插入位置为cur_L的右子树c时,进行旋转前cur_L的平衡因子为1。旋转后cur_L的平衡因子为0,father的平衡因子为-1,cur的平衡因子为0。

右左双旋的代码逻辑如下:

    //右左双旋
	void RotateRL(TreeNode* father)//插入位置在较高右子树的左侧
	{   //father->_fb==2,father->_right->_fb=-1
        assert(father);//根节点不能为空
		TreeNode* rightTree = father->_right;//右子树
		TreeNode* rightTree_left = rightTree->_left;//右子树的左子树
		int RL_fb = rightTree_left->_fb;//旋转前右子树的左子树的平衡因子
		RotateR(rightTree);//先对右子树进行右旋
		RotateL(father);//再对根节点进行左旋
		//修改平衡因子
		if (RL_fb == 0)//右子树的左子树在插入前是空树
		{
			rightTree_left->_fb = 0;
			father->_fb = 0;
			rightTree->_fb = 0;
		}
		else if (RL_fb == -1)//插入位置在右子树的左子树的左边
		{
			rightTree_left->_fb = 0;
			father->_fb = 0;
			rightTree->_fb = 1;
		}
		else if (RL_fb == 1)//插入位置在右子树的左子树的右边
		{
			rightTree_left->_fb = 0;
			father->_fb = -1;
			rightTree->_fb = 0;
		}
		else//未知异常,报错
		{
			assert(false);
		}
	}

模型Ⅳ、根节点的平衡因子为-2,插入节点的子树的平衡因子为1

节点插入前、后的结构模型如下图:

  同样的,将b部分划分成根+左右子树的形态,则结构模型转化成以下形态:

 

 我们同样采取father和cur瓜分cur_R左、右子树,再分别链接链接到cur_R的左、右子树的方式对整棵子树进行重组,得到以下模型:

 观察重组后的模型结构,同样是个与插入前高度相同的AVL树。

而上述重组的过程同样可以拆分成cur左旋+father右旋两个步骤。

操作示意图如下:

 

 观察旋转后的结构模型,我们发现通过cur左旋+father右旋两个步骤同样可以将不平衡的子树调整成与插入前高度相同的AVL树,将该操作称为左右双旋。

接下来只需要修改father、cur、cur_L的平衡因子即可。

同样地,cur_R的平衡因子一定为0,而cur和father的平衡因子与节点在cur_R中的插入位置有关,即与节点插入后cur_R的平衡因子有关。

当插入的子树本身是空树时,h==0。进行旋转前cur_R的平衡因子为0。旋转后cur_R、father、cur的平衡因子都是0。

当插入位置为cur_R的左子树b时,进行旋转前cur_R的平衡因子为-1。旋转后cur_R的平衡因子为0,father的平衡因子为1,而cur的平衡因子为0。

当插入位置为cur_R的右子树c时,进行旋转前cur_R的平衡因子为1。旋转后cur_R的平衡因子为0,father的平衡因子为0,cur的平衡因子为-1。

左右双旋的代码逻辑如下:

    //左右双旋
	void RotateLR(TreeNode* father)//插入位置在较高左子树的右侧
	{   //father->_fb==-2 && father->_left->_fb==1
        assert(father);//根节点不能为空
		TreeNode* leftTree = father->_left;//左子树
		TreeNode* leftTree_right = leftTree->_right;//左子树的右子树
		int LR_fb = leftTree_right->_fb;//插入后左子树的右子树的平衡因子
		RotateL(leftTree);//先将左子树左旋
		RotateR(father);//再将father右旋
		//调整father、leftTree、leftTree_right的平衡因子
		if (LR_fb == 0)//插入位置是空树
		{
			leftTree_right->_fb = 0;
			father->_fb = 0;
			leftTree->_fb = 0;
		}
		else if (LR_fb == -1)//插入位置在左子树的右子树的左侧
		{
			leftTree_right->_fb = 0;
			father->_fb = 1;
			leftTree->_fb = 0;
		}
		else if (LR_fb == 1)//插入位置在左子树的右子树的右侧
		{
			leftTree_right->_fb = 0;
			father->_fb = 0;
			leftTree->_fb = -1;
		}
		else//未知异常
		{
			assert(false);//报错
		}
	}

最后,对插入节点构建二叉树的操作做一个小总结,分为以下三个步骤:

1、在AVL树中找到合适位置插入节点

2、从插入的根节点开始,不断向上调整对应根节点的平衡因子

3、如果向上调整的过程中某个根节点的平衡因子变成-2或2,需要调整该根节点以下整棵子树的结构,使其恢复平衡;并使调整后该子树的高度与节点插入前的该子树的高度相同,避免再往上层调整平衡因子

三、AVL树的验证

通过以下操作来验证一棵二叉树是否为AVL树:

1、中序遍历整棵二叉树,检查其中序序列是否是递增序列

2、遍历二叉树,检查每个根节点的左、右子树高度是否平衡,并检查是否存在异常的平衡因子

中序遍历及左右子树平衡验证的代码如下:

   //中序遍历
	void Inorder()
	{
		Inorder(_root);
	}
    
    //中序遍历(子函数)
	void Inorder(TreeNode* root)
	{
		if (root == nullptr) return;
		Inorder(root->_left);
		cout << "key:" << root->_kv.first << " val:" << root->_kv.second << endl;
		Inorder(root->_right);
	}

    //判断平衡
	bool Isbalance()
	{
		return Isbalance(_root);
	}

    //计算高度
	int Height(TreeNode* root)
	{
		if (root == nullptr) return 0;
		int height_left = Height(root->_left);//左子树高度
		int height_right = Height(root->_right);//右子树高度
		return 1 + max(height_left, height_right);//取左右子树高度较大值+1
	}

	//判断平衡(子函数)
	bool Isbalance(TreeNode* root)
	{
		if (root == nullptr) return true;
		//检查平衡因子
		if (root->_fb < -1 || root->_fb>1)
		{
			cout << root->_kv.first << "的平衡因子为" << root->_fb << ",出现异常" << endl;
			return false;
		}
		//检查高度
		int height_left = Height(root->_left);//左子树高度
		int height_right = Height(root->_right);//右子树高度
		size_t height_dif = abs(height_left-height_right);//高度差的绝对值
		if (height_dif > 1)
		{
			cout << root->_kv.first << "的左右子树高度不平衡,高度差为" << height_dif << endl;
			return false;
		}
		return Isbalance(root->_left) && Isbalance(root->_right);
	}

四、AVL树的删除

AVL树的删除与二叉搜索树的删除类似,不过难点在于平衡因子的更新与结构的调整,这一操作比插入时更为复杂,有可能要进行多次旋转,这里暂且不予讲解。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
AVL是一种自平衡二叉搜索,可以用来实现字典类型。在实现字典类型时,我们可以将键值对存储在AVL的节点中,其中键作为节点的关键字,值则作为节点的附加数据。 下面是一个简单的AVL实现字典类型的Python代码: ```python class AVLNode: def __init__(self, key, val): self.key = key self.val = val self.left = None self.right = None self.height = 1 class AVLTree: def __init__(self): self.root = None def insert(self, key, val): self.root = self._insert(self.root, key, val) def _insert(self, node, key, val): if not node: return AVLNode(key, val) if key < node.key: node.left = self._insert(node.left, key, val) elif key > node.key: node.right = self._insert(node.right, key, val) else: node.val = val node.height = 1 + max(self._height(node.left), self._height(node.right)) balance = self._get_balance(node) if balance > 1 and key < node.left.key: return self._right_rotate(node) if balance < -1 and key > node.right.key: return self._left_rotate(node) if balance > 1 and key > node.left.key: node.left = self._left_rotate(node.left) return self._right_rotate(node) if balance < -1 and key < node.right.key: node.right = self._right_rotate(node.right) return self._left_rotate(node) return node def search(self, key): node = self._search(self.root, key) if node: return node.val else: return None def _search(self, node, key): if not node: return None if key == node.key: return node if key < node.key: return self._search(node.left, key) else: return self._search(node.right, key) def _height(self, node): if not node: return 0 return node.height def _get_balance(self, node): if not node: return 0 return self._height(node.left) - self._height(node.right) def _left_rotate(self, node): new_root = node.right node.right = new_root.left new_root.left = node node.height = 1 + max(self._height(node.left), self._height(node.right)) new_root.height = 1 + max(self._height(new_root.left), self._height(new_root.right)) return new_root def _right_rotate(self, node): new_root = node.left node.left = new_root.right new_root.right = node node.height = 1 + max(self._height(node.left), self._height(node.right)) new_root.height = 1 + max(self._height(new_root.left), self._height(new_root.right)) return new_root ``` 在这个实现中,我们定义了AVLNode类来表示AVL的节点。每个节点包含一个键、一个值、左右子指针以及节点高度。AVLTree类是AVL实现,包含了插入、搜索、左旋和右旋等基本操作。 在insert操作中,我们首先按照二叉搜索的规则找到要插入的位置。然后更新节点高度,并计算平衡因子。如果平衡因子超过了1或-1,我们就需要进行旋转来保持AVL的平衡。 在search操作中,我们按照二叉搜索的规则搜索键值对应的节点,并返回其值。 这个AVL实现可以用来实现字典类型。我们可以将键值对存储在AVL的节点中,并通过搜索操作来查找键对应的值。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值