AVL树的插入

什么是AVL树?

AVL树又称为高度平衡的二叉搜索树。

一棵AVL树如果不是空树,则符合以下性质:
1.左子树与右子树的高度之差的绝对值不超过1
2.左右子树均是AVL树

易知,二叉搜索树可以将查询效率提高至O(logh),h为二叉搜索树的高度。但当数据的插入是严格有序时,二叉搜索的效率会恶化至O(n),即线性复杂度。如下,有一组数据:
在这里插入图片描述
不可否认,这还真是一棵二叉搜索树,但这棵树好嘛?不好!为什么不好?当我们想要查找数据时,查询路径和遍历查数组有什么区别?没有区别!都是线性复杂度。

所以,两个俄罗斯数学家提出了AVL树,其目的在于提高二叉搜索树的效率,减少树的平均搜索长度

那么AVL树又是如何提高效率的?AVL树是这么做的:每插入一个新节点时,就会调整树的结构,使得二叉搜索树保持平衡,从而尽可能降低树的高度,减少树的平均搜索长度。同一组数据,AVL树长这样:
在这里插入图片描述
是二叉搜索树吗?是!

本文AVL树平衡因子bf=右树高度-左树高度,咱们就看看上面这棵树每个节点的平衡因子⑧。
在这里插入图片描述
是AVL树吗?是!无论哪个节点的平衡因子bf(左右子树高度之差)的绝对值不大于1。

而这棵树的搜索长度最长3!比单纯的二叉搜索树长度少了3!提没提高搜索效率?嗯,的确提高了。

那么AVL树凭什么把同样顺序的数据构造成为这样的二叉搜索树呢?那就要详细说说AVL树的插入了!

AVL树的节点插入

前面说了:AVL树每插入一个新节点,都会调整树的结构,使这棵树保持平衡(即每个节点平衡因子bf的绝对值不大于1)

我们先来直观感受一下刚才那个AVL树怎么创建出来的:
在这里插入图片描述小伙伴们应该注意到了里面有一个左旋的操作⑧。不错!左旋是使上面这颗树保持平衡的核心操作!但既然有左旋,当然有右旋了!能旋一次,就能旋两次!还有左右双旋(先左旋再右旋),右左双旋(先右旋再左旋)。打住打住!没了!没有其他旋的种类了。

接下来讲讲四个旋转!

左旋

在这里插入图片描述

我们再来抽象一下左旋操作,及看一下哪种情况需要左旋。
在这里插入图片描述
最后,贴上我当时的一个疑问,权当乐子,如果你也有恰巧有这样的疑惑,看了这番解释懂了,不胜荣幸!
在这里插入图片描述

左旋代码

简要说明:

变量意义
Node*AVL树节点指针
t形参列表里的t即失衡节点
bf节点平衡因子
本文所有旋转函数只是完成从失衡节点开始往下的旋转
void RorateL(Node*& t) {//传入的t是失衡节点
		Node* subL = t;//失衡节点最后会变左树
		t = subL->right;//子节点将变父节点
		subL->right = t->left;//子节点的左树成为父节点的右树
		t->left = subL;//父节点左旋
		subL->bf = t->bf = 0;
	}

【Note】:形参列表传的引用,之所以使用引用,是因为使用引用可以实现真正的修改原指针指向。真实目的是该函数结束后,传入的t是真正的根节点!可以看下图理解一下:
在这里插入图片描述
不一定失衡节点就是整棵AVL树的根节点!这个函数结束后,AVL树长这样,还需要进行一次连接!

右旋

其实右旋就是左旋的镜像,话不多说直接看图,鉴于左旋解释的比较清楚了,这里就不向介绍左旋那样搞了。
在这里插入图片描述来抽象一右旋操作,及看一下哪种情况需要右旋。
在这里插入图片描述

右旋代码
void RorateL(Node*& t) {//传入的t是失衡节点
		Node* subR = t;
		t = subR->left;//子节点将变父节点
		subR->left= t->right;//子节点的右树成为父节点的左树
		t->right = subR;//父节点右旋
		subR->bf = t->bf = 0;
	}

左右双旋

双旋总是会考虑三个节点,这三个节点分别是失衡节点,失衡节点的某一子节点,失衡节点子节点的某一子节点。
在这里插入图片描述
来抽象左右旋操作,及看一下哪种情况需要左右双旋。
在这里插入图片描述

哎?!!忘了在图中说适合左右双旋的情况了!这里就简单说说叭,其实无论你在哪插入新节点,我们都会发现失衡节点subR的平衡因子永远是 -2,而它的子节点subL平衡因子为1大于0,也就是subRsubL,平衡因子前者为负,后者为正,即达成左右双旋的条件==!不要跟我说前者为负,-1会被调整!-1的绝对值根本不大于1,没有失衡,不会发生结构调整!哈哈,开玩笑了,就是提醒一下牢记平衡因子绝对值大于1才会失衡,才会调整,这适合于任何情况

左右双旋代码
void RorateLR(Node*& t) {
		Node* subL, * subR;
		subR = t;
		subL = subR->left;
		t = subL->right;

		subL->right = t->left;
		t->left = subL;
		if (t->bf <= 0)
			subL->bf = 0;
		else 
			subL->bf = -1;
		subR->left = t->right;
		t->right = subR;
		if (t->bf >= 0)
			subR->bf = 0;
		else
			subR->bf = 1;
		t->bf = 0;
	}

右左双旋

同理,右左双旋是左右双旋的镜像。顶不住了,简单说一下:
在这里插入图片描述继续来抽象右左旋操作,及看一下哪种情况需要右左双旋。
在这里插入图片描述

右左双旋代码
void RorateLR(Node*& t) {
		Node* subL, * subR;
		subR = t;
		subL = subR->left;
		t = subL->right;

		subL->right = t->left;
		t->left = subL;
		if (t->bf <= 0)
			subL->bf = 0;
		else 
			subL->bf = -1;
		subR->left = t->right;
		t->right = subR;
		if (t->bf >= 0)
			subR->bf = 0;
		else
			subR->bf = 1;
		t->bf = 0;
	}

AVL的插入函数

上面已经文字+图片把4个旋转解释差不多了,也把什么时候用哪种旋转说清楚了。还遗留几个问题:

1.上面都讲旋转,都是已经插入新节点的前提下在旋转调整,那怎么插入?
答:AVL是BST,所以插入与BST插入一样,但需要使用栈保存插入路线!,否则插入后无法向上回溯调整平衡因子,无法判断是否有节点因此失衡。

2.旋转完成后,就结束了?
答:并没有结束,传入旋转函数的根节点,在旋转完成后成为另一个节点,因此需要进行重新链接,具体情况可以看左旋部分的最后一张图。

3.旋转完成后,也进行了链接,还需要向上回溯继续修改平衡因子吗?
答:不需要,当旋转函数执行后,说明失衡的节点已经平衡,更上层的节点不会失衡,因此不需要继续向上回溯了。

整体代码

#include <iostream>
#include <vector>
#include <stack>
using namespace std;

template<class Ty>
class AVLTree;

template<class Ty>
class AVLNode {
	friend class AVLTree<Ty>;
public:
	AVLNode() :val(Ty()), bf(0),left(nullptr),right(nullptr){}
	AVLNode(Ty _val = Ty(),int _bf = 0, AVLNode<Ty>* l = nullptr, AVLNode<Ty>* r = nullptr) :
		val(_val), bf(_bf),left(l), right(r) {}
	~AVLNode() {
		left = nullptr;
		right = nullptr;
		bf = 0;
	}
private:
	AVLNode<Ty>* left;
	AVLNode<Ty>* right;
	Ty val;
	int bf;
};

template<class Ty>
class AVLTree {
	typedef AVLNode<Ty> Node;
public:
	AVLTree() :root(nullptr) {}
	AVLTree(const vector<Ty>& v) :root(nullptr) {
		for (auto& e : v)
			Insert(e);
	}
public:
	bool Insert(const Ty& data) { return Insert(root, data); }
protected:
	bool Insert(Node*& t, const Ty& data) {
		//先按BST插入节点,寻找插入位置过程中存储轨迹
		Node* parent = nullptr;
		Node* p = t;
		stack<Node*> st;
		while (p){
			if (p->val == data)//重复数据不可插入
				return false;
			parent = p;
			st.push(parent);
			if (data > p->val)
				p = p->right;
			else
				p = p->left;
		}
		p = new Node(data);
		if (!parent) {//空树
			t = p;
			return true;
		}
		if (p->val > parent->val)//左小右大插入
			parent->right = p;
		else
			parent->left = p;
		//调整树,使BST->AVL
		while (!st.empty()) {
			parent = st.top();
			st.pop();
			if (p == parent->left)//左树升高
				--parent->bf;
			else
				++parent->bf;
			if (!parent->bf)//父节点平衡
				break;
			else if (parent->bf == 1 || parent->bf == -1) 
				p = parent;//向上回溯
			else {//本节点不平衡,需要调整
				if (parent->bf < 0) {
					if (p->bf < 0)//右旋(/)
						RorateR(parent);
					else//左右双旋(<)
						RorateLR(parent);
				}
				else {
					if (p->bf > 0)//左旋(\)
						RorateL(parent);
					else//右左双旋(>)
						RorateRL(parent);
				}
				break;//已平衡,跳出
			}
		}
		//因旋转导致的原不平衡的节点被两次指向,需要调整
		if (st.empty())
			t = parent;
		else {
			Node* q = st.top();
			if (parent->val < q->val)
				q->left = parent;
			else
				q->right = parent;
		}
		return true;
	}
private:
	void RorateL(Node*& t) {
		Node* subL = t;
		t = subL->right;//子节点将变父节点
		subL->right = t->left;//子节点的左树成为父节点的右树
		t->left = subL;//父节点左旋
		subL->bf = t->bf = 0;
	}
	void RorateR(Node*& t) {
		Node* subR = t;
		t = subR->left;//子节点将变父节点
		subR->left= t->right;//子节点的右树成为父节点的左树
		t->right = subR;//父节点右旋
		subR->bf = t->bf = 0;
	}
	void RorateLR(Node*& t) {
		Node* subL, * subR;
		subR = t;
		subL = subR->left;
		t = subL->right;

		subL->right = t->left;
		t->left = subL;
		if (t->bf <= 0)
			subL->bf = 0;
		else 
			subL->bf = -1;
		subR->left = t->right;
		t->right = subR;
		if (t->bf >= 0)
			subR->bf = 0;
		else
			subR->bf = 1;
		t->bf = 0;
	}
	void RorateRL(Node*& t) {
		Node* subL, * subR;
		subL = t;
		subR = subL->right;
		t = subR->left;

		subR->left =  t->right;
		t->right = subR;
		if (t->bf >= 0)
			subR->bf = 0;
		else
			subR->bf = 1;

		subL->right = t->left;
		t->left = subL;
		if (t->bf <= 0)
			subL->bf = 0;
		else
			subL->bf = -1;

		t->bf = 0;
	}
private:
	Node* root;
};

结语

写这篇文章时,各种情况的平衡因子我是搞不明白旋转后变成啥样的,但这么一画一写,就基本弄清楚了,也算是有所收获吧。

AVL的删除更显复杂一些,目前肝不出来。不过!等我链接!

不破删除誓不还!

在这里插入图片描述

删除来了来了!
AVL树的删除

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值