AVL树详解与实现(C++)

目录

一、AVL树的概念

​编辑​编辑

二、AVL树结构

三、AVL树插入思想

1.按二叉搜索树规则插入

2.更新平衡因子

四、AVL树旋转

右单旋(LL)

左单旋(RR)

​编辑

左右双旋(LR)

右左双旋(RL)

完整代码



一、AVL树的概念

AVL树是一种 平衡二叉搜索树(Balanced Binary Search Tree)

它由:G. M. Adelson-Velsky,E. M. Landis

两位科学家在1962年提出,因此命名为 AVL Tree。


AVL树的性质:

(1)它是一棵二叉搜索树

(2)左右子树高度差不超过1

平衡因子:

AVL树中每个节点都有:平衡因子

平衡因子 = 右子树高度 - 左子树高度

所以在AVl树中,平衡因子只能等于-1/0/1

如上图,第一个树就是AVL树,而第二个树中,10节点的右子树高度为2,左子树为0,平衡因子为2,所以不是AVL树,这个时候就要通过旋转来处理它


二、AVL树结构

cpp 复制代码
template<class K, class V>
struct AVLTreeNode
{
	// 需要parent指针,后续更新平衡因⼦可以看到
	pair<K, V> _kv;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	int _bf; // balance factor 平衡因子
	AVLTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _bf(0)
	{}
};

template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
      //AVL树的逻辑    
private:
	Node* _root = nullptr;
};

三、AVL树插入思想

AVL插入分两步:

1.按二叉搜索树规则插入

cpp 复制代码
bool Insert(const pair<K, V>& kv)
{
	if (_root == nullptr)
	{
		_root = new Node(kv);
		return true;
	}

	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_kv.first < kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_kv.first > kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}

	cur = new Node(kv);
	if (parent->_kv.first < kv.first)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
      
	cur->_parent = parent;// 链接父亲
}

先按二叉搜索树的规则插入节点,然后再调整

2.更新平衡因子

插⼊结点,会增加⾼度,所以新增结点在parent的右⼦树,parent的平衡因⼦++,新增结点在

parent的左⼦树,parent平衡因⼦--,parent所在⼦树的⾼度是否变化决定了是否会继续往上更新

更新停⽌条件:

• 更新后parent的平衡因⼦等于0,更新中parent的平衡因⼦变化为-1->0或者1->0,说明更新前

parent⼦树⼀边⾼⼀边低,新增的结点插⼊在低的那边,插⼊后parent所在的⼦树⾼度不变,不会

影响parent的⽗亲结点的平衡因⼦,更新结束。

• 更新后parent的平衡因⼦等于1或-1,更新前更新中parent的平衡因⼦变化为0->1或者0->-1,说

明更新前parent⼦树两边⼀样⾼,新增的插⼊结点后,parent所在的⼦树⼀边⾼⼀边低,parent所

在的⼦树符合平衡要求,但是⾼度增加了1,会影响parent的⽗亲结点的平衡因⼦,所以要继续向

上更新。

• 更新后parent的平衡因⼦等于2或-2,更新前更新中parent的平衡因⼦变化为1->2或者-1->-2,说

明更新前parent⼦树⼀边⾼⼀边低,新增的插⼊结点在⾼的那边,parent所在的⼦树⾼的那边更⾼

了,破坏了平衡,parent所在的⼦树不符合平衡要求,需要旋转处理

旋转的⽬标有两个:

1、把parent⼦树旋转平衡。

2、降低parent⼦树的⾼度,恢复到插⼊结点以前的⾼度。所以旋转后也不

需要继续往上更新,插⼊结束。

• 不断更新,更新到根,跟的平衡因⼦是1或-1也停⽌了。因为根节点没有parent节点,不需要再向上更新了


四、AVL树旋转

AVL树共有:

类型 名称
LL 右单旋
RR 左单旋
LR 左右双旋
RL 右左双旋

右单旋(LL)

本图展⽰的是10为根的树,有a/b/c抽象为三棵⾼度为h的⼦树(h>=0),a/b/c均符合AVL树的要

求。10可能是整棵树的根,也可能是⼀个整棵树中局部的⼦树的根。这⾥a/b/c是⾼度为h的⼦树,

是⼀种概括抽象表⽰,他代表了所有右单旋的场景,实际右单旋形态有很多种,具体下图有详细介绍

• 在a⼦树中插⼊⼀个新结点,导致a⼦树的⾼度从h变成h+1,不断向上更新平衡因⼦,导致10的平

衡因⼦从-1变成-2,10为根的树左右⾼度差超过1,违反平衡规则。10为根的树左边太⾼了,需要

往右边旋转,控制两棵树的平衡。

• 旋转核⼼步骤,因为5<b⼦树的值<10,将b变成10的左⼦树,10变成5的右⼦树,5变成这棵树新

的根,符合搜索树的规则,控制了平衡,同时这棵的⾼度恢复到了插⼊之前的h+2,符合旋转原

则。如果插⼊之前10整棵树的⼀个局部⼦树,旋转后不会再影响上⼀层,插⼊结束了。

下面a,b,c都是空树,给a插入节点后,高度变为1,符合右单旋情况

下面a,b,c都是高度为1的树,给a插入节点后,高度变为2,符合右单旋情况

下面a,b,c都是高度为2的树,b,c可以是xyz中任意一种,而a只能是x型,因为我们是给a插入节点,如果是yz型,插入到高的一侧,那么a树自己就不平衡,就要对a树进行旋转,如果是插入到低的一侧,那么a树就变成了x型,5节点的平衡因子不会变,就不需要进行旋转,所以a树只能是x型,插入一个节点后,5的平衡因子变为1,符合右单旋情况

这里abc高度为3,逻辑跟上面是一样,a树只能是x型,插入节点后要么高度变化,要么不变,我们要看对5这里进行右单旋,那么a一定是高度加+1

这里高度一直加下去更复杂,但整体逻辑是不变的,都是对10这个节点进行右单旋

代码实现

cpp 复制代码
void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		if(subLR)
			subLR->_parent = parent;

		Node* pParent = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		if (parent == _root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (pParent->_left == parent)
			{
				pParent->_left = subL;
			}
			else
			{
				pParent->_right = subL;
			}

			subL->_parent = pParent;
		}

		subL->_bf = 0;
		parent->_bf = 0;
	}

这里注意b为空,parent为空的情况,处理细节


左单旋(RR)

左单旋跟右单旋是类似的

本图展示的是10为根的树,有a/b/c抽象为三棵⾼度为h的⼦树(h>=0),a/b/c均符合AVL树的要

求。10可能是整棵树的根,也可能是⼀个整棵树中局部的⼦树的根。这⾥a/b/c是⾼度为h的⼦树,

是⼀种概括抽象表示

• 在a⼦树中插⼊⼀个新结点,导致a⼦树的⾼度从h变成h+1,不断向上更新平衡因⼦,导致10的平

衡因⼦从1变成2,10为根的树左右⾼度差超过1,违反平衡规则。10为根的树右边太⾼了,需要往

左边旋转,控制两棵树的平衡。

• 旋转核⼼步骤,因为10<b⼦树的值<15,将b变成10的右⼦树,10变成15的左⼦树,15变成这棵

树新的根,符合搜索树的规则,控制了平衡,同时这棵的⾼度恢复到了插⼊之前的h+2,符合旋转

原则。如果插⼊之前10整棵树的⼀个局部⼦树,旋转后不会再影响上⼀层,插⼊结束了。

abc的细节跟右单旋是一样,这里不重复讲述

代码实现

cpp 复制代码
void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		Node* parentParent = parent->_parent;
		subR->_left = parent;
		parent->_parent = subR;
		if (parentParent == nullptr)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (parent == parentParent->_left)
			{
				parentParent->_left = subR;
			}
			else
			{
				parentParent->_right = subR;
			}
			subR->_parent = parentParent;
		}

		parent->_bf = subR->_bf = 0;
	}

左右双旋(LR)

上面单旋场景,是纯粹的一边高,也就是给a插入节点,如果是对b插入节点,情况就不一样了

通过下面2张图可以看到,左边⾼时,如果插⼊位置不是在a⼦树,⽽是插⼊在b⼦树,b⼦树⾼度从h变成h+1,引发旋转,右单旋⽆法解决问题,右单旋后,我们的树依旧不平衡。右单旋解决的纯粹的左边⾼,但是插⼊在b⼦树中,10为跟的⼦树不再是单纯的左边⾼,对于10是左边⾼,但是对于5是右边⾼,需要⽤两次旋转才能解决,以5为旋转点进⾏⼀个左单旋,以10为旋转点进⾏⼀个右单旋,这棵树就平衡了。

按照左单旋后仍然是不平衡的

正确双旋过程:

• 上面2张图分别为左右双旋中h==0和h==1具体场景分析,下⾯我们将a/b/c⼦树抽象为⾼度h的AVL⼦树进⾏分析,另外我们需要把b⼦树的细节进⼀步展开为8和左⼦树⾼度为h-1的e和f⼦树,因为我们要对b的⽗亲5为旋转点进⾏左单旋,左单旋需要动b树中的左⼦树。b⼦树中新增结点的位置不同,平衡因⼦更新的细节也不同,通过观察8的平衡因⼦不同,这⾥我们要分三个场景讨论。

• 场景1:h>=1时,新增结点插⼊在e⼦树,e⼦树⾼度从h-1并为h并不断更新8->5->10平衡因⼦,

引发旋转,其中8的平衡因⼦为-1,旋转后8和5平衡因⼦为0,10平衡因⼦为1。

• 场景2:h>=1时,新增结点插⼊在f⼦树,f⼦树⾼度从h-1变为h并不断更新8->5->10平衡因⼦,引

发旋转,其中8的平衡因⼦为1,旋转后8和10平衡因⼦为0,5平衡因⼦为-1。

• 场景3:h==0时,a/b/c都是空树,b⾃⼰就是⼀个新增结点,不断更新5->10平衡因⼦,引发旋

转,其中8的平衡因⼦为0,旋转后8和10和5平衡因⼦均为0。

代码实现

cpp 复制代码
void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;

		RotateL(parent->_left);
		RotateR(parent);

		if (bf == -1)
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == 1)
		{
			subLR->_bf = 0;
			subL->_bf = -1;
			parent->_bf = 0;
		}
		else if (bf == 0)
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

右左双旋(RL)

• 跟左右双旋类似,下⾯我们将a/b/c⼦树抽象为⾼度h的AVL⼦树进⾏分析,另外我们需要把b⼦树的细节进⼀步展开为12和左⼦树⾼度为h-1的e和f⼦树,因为我们要对b的⽗亲15为旋转点进⾏右单

旋,右单旋需要动b树中的右⼦树。b⼦树中新增结点的位置不同,平衡因⼦更新的细节也不同,通

过观察12的平衡因⼦不同,这⾥我们要分三个场景讨论。

• 场景1:h>=1时,新增结点插⼊在e⼦树,e⼦树⾼度从h-1变为h并不断更新12->15->10平衡因

⼦,引发旋转,其中12的平衡因⼦为-1,旋转后10和12平衡因⼦为0,15平衡因⼦为1。

• 场景2:h>=1时,新增结点插⼊在f⼦树,f⼦树⾼度从h-1变为h并不断更新12->15->10平衡因⼦,引发旋转,其中12的平衡因⼦为1,旋转后15和12平衡因⼦为0,10平衡因⼦为-1。

• 场景3:h==0时,a/b/c都是空树,b⾃⼰就是⼀个新增结点,不断更新15->10平衡因⼦,引发旋

转,其中12的平衡因⼦为0,旋转后10和12和15平衡因⼦均为0。

代码实现

cpp 复制代码
void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;
		RotateR(parent->_right);
		RotateL(parent);
		if (bf == 0)
		{
			subR->_bf = 0;
			subRL->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == 1)
		{
			subR->_bf = 0;
			subRL->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			subR->_bf = 1;
			subRL->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

完整代码

写好4种旋转逻辑后,就是分类讨论,什么时候该用哪种旋转

cpp 复制代码
// 控制平衡
// 更新平衡因子
while (parent)
{
	if (cur == parent->_left)
		parent->_bf--;
	else
		parent->_bf++;

	if (parent->_bf == 0)
	{
		break;
	}
	else if (parent->_bf == 1 || parent->_bf == -1)
	{
		cur = parent;
		parent = parent->_parent;
	}
	else if (parent->_bf == 2 || parent->_bf == -2)
	{
		if (parent->_bf == -2 && cur->_bf == -1)
		{
			RotateR(parent);
		}
		else if (parent->_bf == 2 && cur->_bf == 1)
		{
			RotateL(parent);
		}
		else if (parent->_bf == -2 && cur->_bf == 1)
		{
			RotateLR(parent);
		}
		else if (parent->_bf == 2 && cur->_bf == -1)
		{
			RotateRL(parent);
		}
		else
		{
			assert(false);
		}

		break;
	}
	else
	{
		assert(false);
	}
}

这里就是插入节点后更新平衡因子,每更新一次parent就判断parent的_bf,为0说明已经平衡,为-1/1说明还要继续向上更新平衡因子,为-2/2就要旋转了

parent->_bf == -2 && cur->_bf == -1说明左子树是纯粹高,那么就是右单旋

parent->_bf == 2 && cur->_bf == 1 说明右子树是纯粹高,那么就是左单旋

parent->_bf == -2 && cur->_bf == 1 说明左子树不是纯粹高,那么就是左右双旋

parent->_bf == 2 && cur->_bf == -1 说明右子树不是纯粹高,那么就是右左双旋

相关推荐
玉树临风ives1 小时前
atcoder ABC 458 题解
数据结构·c++·算法
chengO_o1 小时前
STL关联式容器:map 与 set 的使用
c++·stl·set·map·平衡二叉搜索树
AKA__Zas1 小时前
芝士算法 (双指针篇2.0)
java·数据结构·leetcode·学习方法
如竟没有火炬1 小时前
有序矩阵中第K小的元素
数据结构·线性代数·算法·leetcode·矩阵·深度优先
charlie1145141911 小时前
现代C++特性指南(5)——RAII 深入理解:资源管理的基石
开发语言·c++·现代c++
神仙别闹2 小时前
基于QT(C++)+Sqlite3实现单词消除游戏系统
c++·qt·sqlite
磊 子2 小时前
AVL树的讲解
数据结构·算法
yunn_2 小时前
基于C++ 11的线程池实现
c++
辞忧九千七2 小时前
吃透Redis7核心数据结构:从基础用法到实战场景(Python版)
开发语言·数据结构·redis·python