AVL树插入详解

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

AVL树插入过程:

1、寻找合适的位置,这一步和搜索二叉树一致
2、判断高度差是否超出1
3、如果超出则进行旋转调整降低高度

定义AVL树的节点

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树类

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

编写插入功能

1、找到合适位置

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了,因为调整后目标树高度
会回到原来高度,就不会对上一层影响了

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

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的调整,称作右单旋

它的镜像操作称作左单旋

在这里插入图片描述
代码实现

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,调整操作称作左右双旋
它的镜像操作是右左双旋

在这里插入图片描述
右左双旋是类似的

代码实现

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);
		}
	}
	//双旋后的平衡因子需要画图分情况讨论,这里不做讲解
  • 19
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Shall#

你的鼓励将是我前进的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值