AVL树(插入)

目录

AVL树的概念

AVL树节点的定义

AVL树的插入

AVL树的旋转

左单旋

右单旋

右左双旋

左右双旋

完整代码


AVL树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当 于在顺序表中搜索元素,效率低下因此,两位俄罗斯的数学家发明了一种解决上述问的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
1.它的左右子树都是AVL树
2.左右子树高度之差(简称平衡因子)的绝对值不超过1
如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在O(log2n)
,搜索时间复杂度O(log2n)。

AVL树节点的定义

AVL树的节点需要包含平衡因子_bf,以平衡因子的值来确定当前树是否需要调整。还需要_parent指针域,以方便在调整的过程中找到其双亲节点。定义如下:
template <class T>
class AVLNode{
public:
	AVLNode(const T& val=T())
		:_parent(nullptr)
		,_right(nullptr)
		, _left(nullptr)
		, _val(val)
		,_bf(0)//平衡因子默认为0,因为刚插入时,没有左右子树
	{

	}
	~AVLNode(){
		Destroy(_root);
	}
	AVLNode<T>* _parent;//指向双亲
	AVLNode<T>* _left;//左指针域
	AVLNode<T>* _right;//右指针域
	T _val;//值域
	int _bf;//平衡因子
};

AVL树的插入

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:
1. 按照二叉搜索树的方式插入新节点
2. 调整节点的平衡因子
代码如下:
template <class T>
class AVLTree{
typedef AVLNode<T> Node;//重命名节点名
public:
	AVLTree()
		:_root(nullptr)
	{

	}
	bool Insert(T val){
		//按照二叉搜索树的方式插入新节点
		if (_root==nullptr){
			_root = new Node(val);
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur){
			parent = cur;
			if (val>cur->_val){
				cur = cur->_right;
			}
			else if(val<cur->_val){
				cur = cur->_left;
			}
			else{
				return false;
			}
		}
		cur = new Node(val);
		cur->_parent = parent;
		if (parent->_val>cur->_val){
			parent->_left = cur;
		}
		else{
			parent->_right = cur;
		}
		//更新平衡因子
		while (parent){parent不空,往上更新
			if (parent->_left==cur){//一进入循环先更新平衡因子
				parent->_bf--;
			}
			else{
				parent->_bf++;
			}
			if (parent->_bf==0){//平衡因子为0,此时树一定平衡,跳出循环
				break;
			}
			else if (parent->_bf==1||parent->_bf==-1){//说明有一条路径中增加了一个节点,可能会违反AVL树的性质,需要向上不断更新
				cur = cur->_parent;
				parent = parent->_parent;
			}
			else{//AVL树的性质遭到破坏,进行调整
				if (parent->_bf==2){
					if (cur->_bf==1){//左单旋
						_RotateL(parent);
					}
					else{
						_RotateRL(parent);//右左双旋
					}
				}
				else{
					if (cur->_bf==-1){
						_RotateR(parent);
					}
					else{
						_RotateLR(parent);//左右双旋
					}
				}
				break;//调整完成后不会违反AVL树的性质了,直接跳出循环
			}
		}
	}
private:
	void Destroy(Node*& root){//销毁AVL树
		if (root==nullptr){
			return;
		}
		Destroy(root->_left);
		Destroy(root->_right);
		delete root;
		root = nullptr;
	}
	Node* _root;//底层维护_root指针
};

以上代码中_root为AVL树底层维护的指针,指向AVL树的根节点,对于插入节点我们并不陌生,在平衡因子为1或者-1时,需要向上调整平衡因子也非常好理解。而当平衡因子为-2或者2时候,就需要特殊处理了,而这种旋转是比较困难的,接下来进行详细讲解。

AVL树的旋转

左单旋

新插入的节点在parent较高右子树的右侧,那么就需要左单旋,具体示意图如下:

如上图,旋转之前此树是不平衡的,经过一次左旋之后,此二叉树就变得平衡了。在上图中,只体现了节点左右指针域的变化,并没有体现_parent指针域的变化,需要在代码中注意_parent指针域的变化,且parent还可能存在_parent节点,旋转后可能还需要改变其左或者右指针域的值。旋转后需要将parent节点的平衡因子以及subR节点的平衡因子修改为0,对应代码如下:

void _RotateL(Node* parent){//左单旋
		Node* pparent = parent->_parent;//记录上parent的双亲节点,可能存在,可能不存在
		Node* subR = parent->_right;//记录上parent的右孩子节点,一定存在
		Node* subRL = subR->_left;//记录上subRL的左孩子节点,可能存在,可能不存在
		parent->_right = subRL;//直接改变parent->_right指向,不用管subRL存不存在
		if (subRL){//如果subRL存在,则改变其_parent指针域的指向
			subRL->_parent = parent;
		}
		parent->_parent = subR;
		subR->_left = parent;
		subR->_parent = pparent;
		if (pparent==nullptr){//最后判断pparent是否为空,如果是,说明parent是根节点,就需要改变_root的指向了
			_root = subR;
		}
		else{//pparent存在,则令pprent的左或者右指向subR
			if (pparent->_left == parent){
				pparent->_left = subR;
			}
			else{
				pparent->_right = subR;
			}
		}
		parent->_bf = 0;//最后修改平衡因子
		subR->_bf = 0;
	}

右单旋

右单旋可以看作是左单旋的反情况,新插入的节点在parent较高左子树的左侧,那么就需要右单旋,具体示意图如下:

右单旋对比左单旋,只是旋转方向变反了,代码的实现形式类似,如下:

void _RotateR(Node* parent){//右单旋
		Node* pparent = parent->_parent;
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		parent->_left = subLR;
		if (subLR){
			subLR->_parent = parent;
		}
		parent->_parent = subL;
		subL->_right = parent;
		subL->_parent = pparent;
		if (pparent==nullptr){
			_root = subL;
		}
		else{
			if (pparent->_left==parent){
				pparent->_left = subL;
			}
			else{
				pparent->_right = subL;
			}
		}
		parent->_bf = 0;
		subL->_bf = 0;
	}

右左双旋

新插入的节点在parent较高右子树的左侧,那么就需要右左双旋了,而右左双旋后,修改平衡因子非常麻烦,需要分情况讨论:

1.parent的左子树为空,那么直接调用右单旋和左单旋即可,图示如下:

把3对应的节点作进行一次右单旋后,在把2对应的节点进行一次左单旋即可。

2. parent的左子树不空,新插入的节点在parent较高右子树的左侧的左侧,图示如下:

对 subR进行一次右单旋后,在对parent进行一次左单旋,旋转完成后,subR的平衡因子不对,需要单独修改为1。可以发现在旋转之前,subRL的平衡因子为-1,根据这一规律修改即可。

3.parent的左子树不空,新插入的节点在parent较高右子树的左侧的右侧,图示如下:

 对 subR进行一次右单旋后,在对parent进行一次左单旋,旋转完成后,parent的平衡因子不对,需要单独修改为-1。可以发现在旋转之前,subRL的平衡因子为1,根据这一规律修改即可。

代码如下:

void _RotateRL(Node* parent){//右左双旋
		Node* subR = parent->_right;//记录parent的_right
		Node* subRL = subR->_left;//记录subR的_left
		int bf = subRL->_bf;//记录subR的平衡因子,根据其值是-1还是1,进而在旋转完成后修改平衡因子
		_RotateR(subR);//对subR及以下部分进行右单旋
		_RotateL(parent);//对parent及以下部分进行左单旋
		if (bf==1){//根据bf的值,修改parent或者subR的平衡因子
			parent->_bf = -1;
		}
		else if (bf==-1){
			subR->_bf = 1;
		}
	}

左右双旋

左右双旋和右边双旋的实现方式类似,新插入的节点在parent较高左子树的右侧,需要进行右左双旋,分情况讨论如下:

1.parent的右子树为空,那么直接调用左单旋和右单旋即可,图示如下:

 把1对应的节点作进行一次左单旋后,在把3对应的节点进行一次右单旋即可。

2.parent的右子树不空,新插入的节点在parent较高左子树的右侧的右侧,图示如下: 

对subL进行一次左单旋后,在对parent进行一次右单旋,旋转完成后,subL的平衡因子不对,需要单独修改为-1。可以发现在旋转之前,subLR的平衡因子为1,根据这一规律修改即可。

3.parent的右子树不空,新插入的节点在parent较高左子树的右侧的左侧,图示如下: 

对subL进行一次左单旋后,在对parent进行一次右单旋,旋转完成后,parent的平衡因子不对,需要单独修改为1。可以发现在旋转之前,subLR的平衡因子为-1,根据这一规律修改即可。

代码如下:

void _RotateLR(Node* parent){//左右双旋
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;
		_RotateL(parent->_left);
		_RotateR(parent);
		if (bf==1){
			subL->_bf = -1;
		}
		else if (bf==-1){
			parent->_bf = 1;
		}
	}

完整代码

#pragma once
#include <iostream>
using namespace std;
template <class T>
class AVLNode{
public:
	AVLNode(const T& val=T())
		:_parent(nullptr)
		,_right(nullptr)
		, _left(nullptr)
		, _val(val)
		,_bf(0)//平衡因子默认为0,因为刚插入时,没有左右子树
	{

	}
	~AVLNode(){
		Destroy(_root);
	}
	AVLNode<T>* _parent;//指向双亲
	AVLNode<T>* _left;//左指针域
	AVLNode<T>* _right;//右指针域
	T _val;//值域
	int _bf;//平衡因子
};
//默认没有重复元素
template <class T>
class AVLTree{
typedef AVLNode<T> Node;//重命名节点名
public:
	AVLTree()
		:_root(nullptr)
	{

	}
	bool Insert(T val){
		//按照二叉搜索树的方式插入新节点
		if (_root==nullptr){
			_root = new Node(val);
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur){
			parent = cur;
			if (val>cur->_val){
				cur = cur->_right;
			}
			else if(val<cur->_val){
				cur = cur->_left;
			}
			else{
				return false;
			}
		}
		cur = new Node(val);
		cur->_parent = parent;
		if (parent->_val>cur->_val){
			parent->_left = cur;
		}
		else{
			parent->_right = cur;
		}
		//更新平衡因子
		while (parent){
			if (parent->_left==cur){//一进入循环先更新平衡因子
				parent->_bf--;
			}
			else{
				parent->_bf++;
			}
			if (parent->_bf==0){//平衡因子为0,此时树一定平衡,跳出循环
				break;
			}
			else if (parent->_bf==1||parent->_bf==-1){//说明有一条路径中增加了一个节点,可能会违反AVL树的性质,需要向上不断更新
				cur = cur->_parent;
				parent = parent->_parent;
			}
			else{//AVL树的性质遭到破坏,进行调整
				if (parent->_bf==2){
					if (cur->_bf==1){//左单旋
						_RotateL(parent);
					}
					else{
						_RotateRL(parent);//右左双旋
					}
				}
				else{
					if (cur->_bf==-1){
						_RotateR(parent);
					}
					else{
						_RotateLR(parent);//左右双旋
					}
				}
				break;
			}
		}
	}
	void InOrder(){
		cout << "中序遍历结果为:" << endl;
		_InOrder(_root);
		cout << endl;
	}
	bool InvalidAVL(){
		return _InvalidAVL(_root);
	}
private:
	int GetLength(Node* root){//计算以_root为根的二叉树深度
		if (root==nullptr){
			return 0;
		}
		int left = GetLength(root->_left) + 1;
		int right = GetLength(root->_right) + 1;
		return left > right ? left : right;
	}
	bool _InvalidAVL(Node* root){//检查构建的二叉搜索树是否正确
		if (root==nullptr){
			return true;
		}
		int left = GetLength(root->_left);
		int right = GetLength(root->_right);
		if (right-left!=root->_bf){
			cout << "Node:" << root->_val << endl;
			cout << right - left << " " << root->_bf << endl;
			return false;
		}
		return _InvalidAVL(root->_left)&&_InvalidAVL(root->_right);
	}
	void _InOrder(Node* root){//中序遍历
		if (root==nullptr){
			return;
		}
		_InOrder(root->_left);
		cout << root->_val << ' ';
		_InOrder(root->_right);
	}
	void _RotateL(Node* parent){//左单旋
		Node* pparent = parent->_parent;//记录上parent的双亲节点,可能存在,可能不存在
		Node* subR = parent->_right;//记录上parent的右孩子节点,一定存在
		Node* subRL = subR->_left;//记录上subRL的左孩子节点,可能存在,可能不存在
		parent->_right = subRL;//直接改变parent->_right指向,不用管subRL存不存在
		if (subRL){//如果subRL存在,则改变其_parent指针域的指向
			subRL->_parent = parent;
		}
		parent->_parent = subR;
		subR->_left = parent;
		subR->_parent = pparent;
		if (pparent==nullptr){//最后判断pparent是否为空,如果是,说明parent是根节点,就需要改变_root的指向了
			_root = subR;
		}
		else{//pparent存在,则令pprent的左或者右指向subR
			if (pparent->_left == parent){
				pparent->_left = subR;
			}
			else{
				pparent->_right = subR;
			}
		}
		parent->_bf = 0;//最后修改平衡因子
		subR->_bf = 0;
	}
	void _RotateR(Node* parent){//右单旋
		Node* pparent = parent->_parent;
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		parent->_left = subLR;
		if (subLR){
			subLR->_parent = parent;
		}
		parent->_parent = subL;
		subL->_right = parent;
		subL->_parent = pparent;
		if (pparent==nullptr){
			_root = subL;
		}
		else{
			if (pparent->_left==parent){
				pparent->_left = subL;
			}
			else{
				pparent->_right = subL;
			}
		}
		parent->_bf = 0;
		subL->_bf = 0;
	}
	void _RotateLR(Node* parent){//左右双旋
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;
		_RotateL(parent->_left);
		_RotateR(parent);
		if (bf==1){
			subL->_bf = -1;
		}
		else if (bf==-1){
			parent->_bf = 1;
		}
	}
	void _RotateRL(Node* parent){//右左双旋
		Node* subR = parent->_right;//记录parent的_right
		Node* subRL = subR->_left;//记录subR的_left
		int bf = subRL->_bf;//记录subR的平衡因子,根据其值是-1还是1,进而在旋转完成后修改平衡因子
		_RotateR(subR);//对subR及以下部分进行右单旋
		_RotateL(parent);//对parent及以下部分进行左单旋
		if (bf==1){//根据bf的值,修改parent或者subR的平衡因子
			parent->_bf = -1;
		}
		else if (bf==-1){
			subR->_bf = 1;
		}
	}
	void Destroy(Node*& root){//销毁AVL树
		if (root==nullptr){
			return;
		}
		Destroy(root->_left);
		Destroy(root->_right);
		delete root;
		root = nullptr;
	}
	Node* _root;//底层维护_root指针
};

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值