AVL树插入详解

在极端情况下,搜索二叉树会退化成线性表,查找效率变得低下,根本原因是因为搜索二叉树左右子树的高度极大程度上取决于根节点,为了解决这种情况,AVL树和红黑树被相继提出,这两种数据结构都是在搜索二叉树的基础上通过适当的调整来保持左右子树近似相等。

AVL树插入过程:

1、寻找合适的位置,这一步和搜索二叉树一致

2、判断高度差是否超出1

3、如果超出则进行旋转调整降低高度

定义AVL树的节点

cpp 复制代码
template<class K>		//以set为例
struct AVLTreeNode{
	K _key;
	AVLTreeNode<K>* _left;
	AVLTreeNode<K>* _right;
	AVLTreeNode<K>* _parent;
	int _bf;
	/*_bf作为平衡因子,假设平衡因子的计算公式 右树高度-左树高度
	AVL树规定左右子树高度差不能超过1,因此正常情况下_bf只可能是
	0、1、-1
	_parent代表该节点的父节点,之所以存在是因为在后续调整中能够快
	速定位到父节点*/
	AVLTreeNode(const K& key)
		:_key(key)
		,_left(nullptr)
		,_right(nullptr)
		,__parent(nullptr)
		,_bf(0)
	{}//新增节点一定叶节点,左右子树高度均为0,故_bf=0
};

定义AVL树类

cpp 复制代码
template<class K>
class AVLTree{
	typedef AVLTreeNode<K> Node;
public:
		......
private:
	Node* _root=nullptr;
};

编写插入功能

1、找到合适位置

cpp 复制代码
bool Insert(const K& key){
	Node* cur=_root,* parent=nullptr;
	if(cur==nullptr){
		_root=new Node(key);
		return true;
	}
	while(cur){
		parent=cur;
		if(key<cur->_key)
			cur=cur->_left;
		else if(key>cur->_key)
			cur=cur->_right;
		else
		 return false;
	}
	Node* cur=new Node(key);
	if(key<parent->_left)
		cur=parent->_left;
	else
		cur=parent->_right;
	cur->_parent=parent;  // 不要忘记更新
	...............
	.......
	return true;

2、判断是否需要调整:

h代表高度为h的抽象树,图中给出的例可能是一颗子树,也可能是一颗完整的树

根据上述分析,插入节点后马上要做的事情就是先向上更新平衡因子

插入的节点在父节点右,父节点bf加1

反之父节点bf减1

再看父节点的平衡因子

如果为0,则说明左右子树高度相等,即没有打破规则

通俗地理解为在较矮的子树插入
如果为1 or -1,说明左右子树高度出现了一端高的情况,

那么也一定会影响到父节点的父节点的平衡因子

需要继续向上调整
如果为2 or -2,那就需要调整了,设计到的旋转操作见下文

值得注意的是,调整过后我们就可以break了,因为调整后目标树高度

会回到原来高度,就不会对上一层影响了

那么就先写更新平衡因子的代码片段吧

cpp 复制代码
while (parent) {
		if (cur == parent->_right)
		{
			parent->_bf++;
		}
		else
		{
			parent->_bf--;
		}
		if (parent->_bf == 0) {
			break;
		}
		else if (parent->_bf == 1 || parent->_bf == -1)
		{
			// 继续更新
			parent = parent->_parent;
			cur = cur->_parent; 
		}
		else if (parent->_bf == 2 || parent->_bf == -2) {
			//旋转调整
		}
		else {
			assert(false);
		}
	}

3、旋转操作

针对情况3的调整,称作右单旋

它的镜像操作称作左单旋


代码实现

cpp 复制代码
void RotateR(Node* parent){		//右单旋
		Node* subR = parent->_left;
		Node* pparent = parent->_parent;
		Node* subRR = subR->_right;
		/*parent代表要进行旋转的节点,例如图中的30
		ppparent代表要旋转节点的父节点
		subR代表目标节点的左节点
		subRR代表目标节点左节点的右节点
		*/
		parent->_left = subRR;
		if (subRR) {
			subRR->_parent = parent;
		}

		subR->_right = parent;
		if (pparent) {
			if (pparent->_left == parent) {
				pparent->_left = subR;
			}
			else {
				pparent->_right = subR;
			}
		}
		else {
			_root = subR;	//针对目标节点是根节点的情况
		}

		subR->_parent = pparent;
		parent->_parent = subR;

		subR->_bf = parent->_bf = 0;//不要忘记更新
	}

	//左单旋RotateL同理

针对情况2,调整操作称作左右双旋

它的镜像操作是右左双旋


右左双旋是类似的

代码实现

cpp 复制代码
void RotateLR(Node* parent) {	//左右双旋
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;
		RotateL(parent->_left);
		RotateR(parent);

		//更新bf
		if (bf == 1) {		//c位置插入
			subL->_bf = -1;
			subLR->_bf = parent->_bf = 0;
		}
		else if (bf == -1) {			//b位置插入
			parent->_bf = 1;
			subLR->_bf = subL->_bf = 0;
		}
		else if (bf == 0) {
			parent->_bf = subL->_bf = subLR->_bf = 0;
		}
		else {
			assert(false);
		}
	}
	//双旋后的平衡因子需要画图分情况讨论,这里不做讲解
相关推荐
小妖6664 小时前
js 实现快速排序算法
数据结构·算法·排序算法
三水不滴5 小时前
计网ping原理
经验分享·笔记·计算机网络
王老师青少年编程5 小时前
2024年信奥赛C++提高组csp-s初赛真题及答案解析(阅读程序第3题)
c++·题解·真题·csp·信奥赛·csp-s·提高组
prog_61035 小时前
【笔记】思路分享:各种大模型免费当agent后台
笔记·大语言模型·agent·cursor
凡人叶枫5 小时前
C++中输入、输出和文件操作详解(Linux实战版)| 从基础到项目落地,避坑指南
linux·服务器·c语言·开发语言·c++
CSDN_RTKLIB5 小时前
使用三方库头文件未使用导出符号情景
c++
凯尔萨厮5 小时前
Maven(Windows下载安装)
笔记·maven
wdfk_prog5 小时前
[Linux]学习笔记系列 -- [drivers][input]serio
linux·笔记·学习
菩提小狗7 小时前
小迪安全2023-2024|第5天:基础入门-反弹SHELL&不回显带外&正反向连接&防火墙出入站&文件下载_笔记|web安全|渗透测试|
笔记·安全·web安全
rainbow68897 小时前
Linux文件描述符与重定向原理
c++