avl平衡树

本文深入解析AVL树的概念、性质与操作,包括插入、删除、旋转等关键步骤,并提供两种实现方式的代码示例,助您掌握平衡二叉搜索树的核心原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、本博客主要内容:
1、avl平衡树的概念,性质
2、avl平衡树插入节点方式图解
3、avl平衡树两种实现方式+代码讲解
(1)、平衡树节点结构
(2)、插入节点
(3)、旋转节点
(4)、查找节点
(5)、删除节点
(6)、平衡判断
4、完整代码链接

二、平衡树的性质:
1、平衡树是一个高度平衡的二叉搜索树,左右子树的高度差不超过1,并且左右子书都是平衡树
2、平衡树满足二叉搜索树的性质:左子树节点 < 根节点 < 右子树
3、平衡树的查找,插入,删除效率为O(lgN)
4、avl树是一个严格的平衡树,插入和删除操作需大量的旋转调平操作,以及平衡因子的计算处理,所以效率没有红黑树的效率高,应用也无红黑树多。

三、avl树插入节点图解:
在这里插入图片描述
四、avl树操作代码讲解
avl树实现方法1:(递归实现)
1、节点结构:

struct AVLNode {
	int _key;
	int _value;
	int _hr;  //表示树的高度
	AVLNode* _left;
	AVLNode* _right;

	AVLNode(int key = 0, int value = 0)
		:_key(key)
		, _value(value)
		, _hr(1)  //高度从1开始
		, _left(NULL)
		, _right(NULL)
	{}
};

2、获取树的高度及更新操作

int getHr(Node* node) {
	if (node) {
		return node->_hr;
	}
	return 0;
}
//树的高度=左右子树高度较大的+1(根节点)
void upHr(Node* &node) {
	if (node) {
		int lhr = getHr(node->_left);
		int rhr = getHr(node->_right);
		node->_hr = (lhr > rhr? lhr : rhr) + 1;  //树的高度为较高的高度值+1
	}
}

3、插入节点
方法:
1、判断节点是否为空,空则返回一个新节点(new);
2、先递归查找插入节点的位置(注:要接受返回的节点)
3、更新节点的hr
4、根据计算左右子树的hr差值,选择对应的旋转方式。
5、返回当前处理的节点,依据递归的回退,继续处理上层节点(循环2-4)

void put(int key, int value) {
	_root = _put(_root, key, value);
}
//递归插入节点
Node* _put(Node* root, int key, int value) {
	if (root == NULL) {
		return new Node(key,value);
	}
	//查找插入位置
	if (root->_key > key) {
		root->_left = _put(root->_left, key, value);
	}
	else if (root->_key < key) {
		root->_right = _put(root->_right, key, value);
	}
	else {  //已存在key,更新value
		root->_value = value;
	}
	//递归更新每个节点的高和旋转调平
	//1、更新树的高度
	upHr(root);
	//2、旋转节点
	if (getHr(root->_right)-getHr(root->_left) == -2) {  //左子树比右子树高超过1
		Node* child = root->_left;
		if (getHr(child->_right)-getHr(child->_left) == 1) {  //双旋
			root->_left = rotateLeft(child);  //左旋
		}
		root = rotateRight(root);//右旋
	}
	if (getHr(root->_right) - getHr(root->_left) == 2) {  //右子树比左子树高超过1
		Node* child = root->_right;
		if (getHr(child->_right) - getHr(child->_left) == -1) {  //双旋
			root->_right = rotateRight(child);//右旋
		}
		root = rotateLeft(root); //左旋
	}
	return root;
}

4、旋转操作
1、左旋转:
1、交换旋转节点和旋转节点的右子节点
2、将旋转节点的右子节点的左孩子挂在旋转节点的右侧
3、更新旋转节点和旋转节点的右孩子的hr值
4、返回旋转节点的右子节点(作为新的旋转节点位置的节点)

Node* rotateLeft(Node* node) {
	Node* node_right = node->_right;
	Node* node_rleft = node_right->_left;
	//交换节点的位置
	node_right->_left = node;
	node->_right = node_rleft;
	//更新旋转两个节点的高
	upHr(node); 
	upHr(node_right);
	return node_right;
}

2、右旋转方法与左旋转类似

Node* rotateRight(Node* node) {
	Node* node_left = node->_left;
	Node* node_lright = node_left->_right;
	//交换节点的位置
	node_left->_right = node;
	node->_left = node_lright;
	//更新节点的高
	upHr(node);
	upHr(node_left);
	return node_left;
}

6、删除节点
删除节点方式与二叉搜索树操作类似,只是多了删除后重新计算hr值和旋转调平操作
1、节点为空,返回NULL
2、递归查找删除节点(注:接收返回值)
3、找到删除节点:
(1)、删除节点的左为空,删除该节点并返回节点的右孩子
(2)、删除节点的右为空,删除该节点并返回节点的左孩子
(3)、查找替换节点(在节点的左子树找最大值,或右子树找最小值),交换删除节点和替换节点的值(value和key),继续递归删除替换节点(节点的右孩子/左孩子,原key节点)。返回替换节点。
4、更新节点的hr
5、计算hr进行旋转调平

//删除节点
void erase(int key) {
	_root = _erase(_root, key);
}
Node* _erase(Node* root, int key) {
	if (root == NULL) return NULL;
	if (root->_key > key) {
		root->_left = _erase(root->_left, key);
	}
	else if (root->_key < key) {
		root->_right = _erase(root->_right, key);
	}
	else {
		Node* del = root;
		if (del->_left == NULL) {
			root = del->_right;
			delete del;
			return root;
		}
		if (del->_right == NULL) {
			root = del->_left;
			delete del;
			return root;
		}
		//1、寻找替换节点,删除替换节点
		Node* min_right = getMin(root->_right);
		swap(root->_key, min_right->_key);
		swap(root->_value, min_right->_value);
		root->_right = _erase(root->_right, key);  //在替换节点位置去删除该节点
		return root;  //返回替换节点
	}
	//平衡处理
	//1、重新计算hr
	upHr(root);
	//2、旋转处理
	if (getHr(root->_right) - getHr(root->_left) == -2) {  //左子树比右子树高超过1
		Node* child = root->_left;
		if (getHr(child->_right) - getHr(child->_left) == 1) {  //双旋
			root->_left = rotateLeft(child);  //左旋
		}
		root = rotateRight(root);//右旋
	}
	if (getHr(root->_right) - getHr(root->_left) == 2) {  //右子树比左子树高超过1
		Node* child = root->_right;
		if (getHr(child->_right) - getHr(child->_left) == -1) {  //双旋
			root->_right = rotateRight(child);//右旋
		}
		root = rotateLeft(root); //左旋
	}
	return root;
}

6、平衡判断
1、通过计算hr的差值判断是否平衡

//平衡判断
bool isBlance() {
	return _isBlance(_root);
}
bool _isBlance(Node* root) {
	if (root == NULL) {
		return true;
	}
	int left_hr = getHr(root->_left);
	int right_hr = getHr(root->_right);
	if (right_hr - left_hr > -2 && right_hr - left_hr < 2) {  //根据左右子树的高度差判断
		return true;
	}
	return _isBlance(root->_left) && _isBlance(root->_right);
}

7、查找节点(key)

int get(int key) {
	Node* node = _get(_root, key);
	if (node) {
		return node->_value;
	}
	return -1;  //未找到返回-1
}
Node* _get(Node* root, int key) {
	if (root == NULL) return NULL;
	if (root->_key > key) {
		return _get(root->_left, key);
	}
	else if(root->_key < key){
		return _get(root->_right, key);
	}else{
		return root;
	}
}

平衡树的其它操作与二叉搜索树操作类,此处不在重述,可以看二叉查找(搜索)树的操作实现即可。

五、avl树实现方法2(非递归):
1、节点结构:

template<class T,class V>
struct AvlNode
{
	T _key;
	V _value;   
	int _bf;  //平衡因子
	AvlNode<T,V>* _left;
	AvlNode<T,V>* _right;
	AvlNode<T,V>* _parent;
	AvlNode(const T& key,const V& value)
		:_key(key)
		,_value(value)
		,_bf(0)  //bf表示平衡因子
		,_left(NULL)
		,_right(NULL)
		,_parent(NULL)  //用于回退处理上层操作
	{}
};

1、节点插入操做(非递归)
平衡处理:
1、添加在较高右子树的右节点上(右节点的右或左),右比左长—左旋
2、添加在较高左子树的左节点上(左节点的左或右),左比右长—右旋
3、添加在较高右子树的左节点上(左节点的左或右)–右比左长—右,左双旋,先右旋成1种,在进行左旋
4、添加在较高左子树的右节点上(右节点的左或右)–左比右长—左,右双旋,先左旋成2种,在进行右旋

bool Push_Back(const T& Key,const V& value)
{
	if(_root == NULL)
	{
		_root = new Node(Key,value);//根节点
		return true;
	}
	Node* cur = _root;
	Node* parent = NULL;
	//遍历查找到可插入位置
	while(cur)
	{
		if(cur->_key < Key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if(cur->_key > Key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else//数值相同错误
			return false;
	}
	//链接新节点
	cur = new Node(Key,value);
	if(parent->_key < Key)//插入在右侧
	{
		parent->_right = cur;
	}
	else//插入在左侧
	{
		parent->_left = cur;
	}
	cur->_parent = parent;//初始新节点的父亲
	//处理平衡因子
	while(parent)  //循环处理
	{
		//平衡因子计算(处理双亲(父)节点的因子)
		if(parent->_left == cur)//插入在双亲的左
			parent->_bf--;      //平衡因子减-1
		else                    //插入在双亲的右
			parent->_bf++;      //平衡因子+1
		//根据平衡因子的数值判断是否调整树
		if(parent->_bf == 0)//平衡不用调整树,退出
			break;
		else if(parent->_bf == 1 || parent->_bf == -1)//不平衡,因子为1--向上回退
		{
			cur = parent;
			parent = cur->_parent;
		}
		//需平衡调整树
		else//不平衡,因子为2,平衡处理
		{
			if(parent->_bf == 2)//树的右侧长
			{
				if(cur->_bf == -1)//插入在右子树节点左侧
					Lotatel(parent);//左单旋
				else //插入在右子树节点右侧
					Rotaterl(parent);//右左旋
			}
			else//树的左侧长
			{
				if(cur->_bf == -1)  //插入在右子树节点左侧
					Rotatel(parent);//右旋
				else                //插入在右子树节点左侧
					Rotatelr(parent);//左右旋
			}
			break;
		}
	}
	return true;
}

3、左旋转

void Lotatel(Node* parent)//左旋---父节点的右长
{
	//parent是因子值为2的节点
	Node* subr = parent->_right;//获取父节点的右子树
	Node* subl = subr->_left;   //获取右子树的左孩子

	//改变指针域,实现树的调整
	parent->_right = subl;     //父节点的右指向subr的左孩子
	if(subl){//右子树存在左孩子
		subl->_parent = parent;//更改subr左孩子的父节点
	}
	subr->_left = parent;    //更改subr的左指域指向父节点
	Node* pparent = parent->_parent;//获取原父节点的父亲
	subr->_parent = pparent;//更新subr父亲节点的父亲
	parent->_parent = subr;//subr当作新的父亲节点

	if(parent == _root)//父节点为根节点
		_root = subr;//更改根节点
	else
	{
		if(pparent->_left == parent)//原父亲节点在左侧
			pparent->_left = subr;//新父亲节点连接在左侧(原父亲节点的父亲节点左侧)
		else
			pparent->_right = subr;////新父亲节点连接在右侧
	}
	//更新节点因子值
	parent->_bf = subr->_bf = 0;
	parent->_parent = NULL;
}

4、右旋转

void Rotatel(Node* parent)//右旋
{
	Node* subl = parent->_left;
	Node* sublr = subl->_right;

	parent->_left = sublr;
	if(sublr){   //存在父节点的右子树存在左节点
		sublr->_parent = parent;//更改其父节点
	}
	subl->_right = parent;
	Node* pparent = parent->_parent;//原父节点的父亲
	subl->_parent = pparent;
	parent->_parent = subl;
	if(parent == _root)//父节点为根节点
		_root = subl;//更改根节点
	else
	{
		if(pparent->_left == parent)
			pparent->_left = subl;
		else
			pparent->_right = subl;
	}
	//更新节点因子
	parent->_bf = subl->_bf = 0;
	parent->_parent = NULL;
}

5、左,右双旋

void Rotatelr(Node* parent)//左,右双旋
{
	Node* subl = parent->_left;
	Node* sublr = subl->_right;
	int bf = sublr->_bf;//记录旋转之前的因子值
	Lotatel(parent->_left);//左旋
	Rotatel(parent);//右旋
	//更新因子值
	if(bf == -1)
		subl->_bf = 1;
	else if(bf == 1)
		parent->_bf = -1;
}

6、右,左双旋

void Rotaterl(Node* parent)//右,左双旋
{
	Node* subr = parent->_right;
	Node* subrl = subr->_left;
	int bf = subrl-> _bf;//记录旋转之前的
	Rotatel(parent->_right);//右旋
	Lotatel(parent);//左旋
	if(bf == 1)
		parent->_bf = -1;
	else if(bf == -1)
		subr->_bf = 1;
}

7、计算树的高度

size_t Height(Node* cur)
{
	if(cur == NULL)  return 0;
	static size_t n1 = 1;//根节点为第一层
	static size_t n2 = 1;

	n1 = Height(cur->_left);
	n2 = Height(cur->_right);
	if(cur->_left || cur->_right)//在有左或右子树的节点才进行比较
	return (n1 > n2 ? n1 : n2)++;
	return 1;//只有根节点
}

六、代码链接
1、avl树实现代码1:https://github.com/weienjun/-algorithm/tree/master/avl

2、avl树实现代码2:https://github.com/weienjun/-algorithm/tree/master/avl2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值