首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >让我来告诉你如何实现AVL树(C++)

让我来告诉你如何实现AVL树(C++)

作者头像
Yuzuriha
发布2026-01-14 15:55:26
发布2026-01-14 15:55:26
210
举报
文章被收录于专栏:Linux网络Linux网络

概念

        1.AVL树是最早发明的自平衡二叉树。AVL树是一颗空树or一个棵左右子树都为符合AVL树,且左右两边子树的高度差的绝对值不大于1。

        2.对于实现AVL树,我们这里引入了平衡因子(banlance factor 简写为bf)这个概念帮助我们实现AVL树。AVL树的每一个节点都有平衡因子,平衡因子=右子树的高度-左子树高度。

        3.其实AVL树是实现不一定要用平衡因子,只是使用平衡因子可以更好的帮助我们观察与控制AVL树的平衡

平衡因子的更新

        1.平衡因子=右子树高度-左子树高度

        2.只有当左右子树发生了高度变化才会更新平衡因子

        3.插入左子树时,平衡因子--。插入有子树时,平衡因子++

        4.parent的平衡因子是否变化决定了是否继续向上更新

停止更新条件

        1.当更新之后parent的bf等于0,说明更新之前parent子树是一边高一边低(-1->0  or  1->0),更新之后使其两边都一样高,parent子树并没有高度变化,不会影响parent的父亲节点,所以停止更新

        2.当更新之后parent的|bf|=1,说明更新之前parent子树左右两边一样高(0 -> 1)更新之后使左右两边一边高一边低,parent子树的高度发生改变,会影响parent的父亲节点,所以继续向上更新

        3.当更新之后parent的|bf|=2,已经破坏平衡,停止更新,并调整结构使其重新达到平衡

        4.一直更新直到更新到根节点,停止更新

实现

AVL树结构的实现

代码语言:javascript
复制
template<class K, class V>
struct AVLTreeNode
{
	// 需要parent指针,后续更新平衡因子可以看到 
	pair<K, V> _kv;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	int _bf; // balance factor 平衡因子

	AVLTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _bf(0)
	{}
};


template<class k, class v>
class AVLTree
{
public:
	//typedef AVLTreeNode<k, v> Node;
	using Node = AVLTreeNode<k, v>;

    //
    //
    //
    //

private:
	Node* _root = nullptr;
}

AVL树的插入

        当|bf|=0直接停止更新,当|bf|=1时使用循环直接向上更新,这两个情况后续我会在代码中直接展现不过多讨论。我们真正需要关注的是当|bf|=2时,我们应该如何调整使其重新回归平衡

单旋
    右单旋:

        ​​​​​由于左边插入节点导致左边高度太高,我们可以将parent向右旋转,让其左右高度平衡。旋转的核心是subRL这个节点,subR<subRL<parent。旋转之后仍满足搜索二叉树的规则,并且控制了高度

        调整完成,更新各个节点的平衡因子后,就停止更新了

代码语言:javascript
复制
//右单旋
void RotateR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;

	Node* pParent = parent->_parent;
	subL->_right = parent;
	parent->_parent = subL;

	parent->_left = subLR;
	if(subLR)
		subLR->_parent = parent;

	//判断:若旋转的节点为根,则代表这是一颗完整的树,不需要处理
	//若旋转的节点不为根,则代表这是一颗子树,需要连接至上面的树
	if (pParent == nullptr)
	{
		_root=subL;
		subL->_parent = nullptr;
		
	}
	else
	{
		if (pParent->_left == parent)
		{
			pParent->_left = subL;
			subL->_parent = pParent;
		}
		else
		{
			pParent->_right = subL;
			subL->_parent = pParent;
		}
	}

	//更新平衡因子
	subL->_bf = parent->_bf = 0;

}
        左单旋:

        与右单旋同理,只是方向反了一下 

代码语言:javascript
复制
//左单旋
void RotateL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	
	parent->_right = subRL;
	if(subRL)
		subRL->_parent = parent;

	Node* pParent = parent->_parent;
	subR->_left = parent;
	parent->_parent = subR;

	if (pParent == nullptr)
	{
		_root = subR;
		subR->_parent= nullptr;
	}
	else
	{
		if (pParent->_left == parent)
		{
			pParent->_left = subR;
		}
		else
		{
			pParent->_right = subR;
		}
		subR->_parent = pParent;
	}

	//更新平衡因子
	subR->_bf = parent->_bf = 0;

}
双旋 
        左右双旋:

        先给出一个简单情况帮助我们理解一下,左右双旋与前面的右单旋的区别与联系。由图我们可以看出,左右双选与右单旋都是左边高,仅仅是插入的位置不同

        总的来说左右双旋的插入位置在subL的右边,如图所示。但是subL的右子树也存在左右子树所以我们应该要分类讨论一下这个细节 

        新增节点插在e时:

         新增节点插在f时:​​​​​​​

        由图我们可以看到,其实对于左右双旋来说,插在e还是f点对结果的影响就是subL与parent的平衡因子不同,旋转的步骤是一模一样的。所以我们不管是插入在e还是f,对于左边双旋的步骤都为:1.左旋subL 2.右旋parent  3.记录subLR旋转前的平衡因子,判断插入的位置,根据插入的位置来更新平衡因子

代码语言:javascript
复制
//左右双旋
void RotateLR(Node* parent)
{
	//提前记录,插入的位置
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	int bf = subLR->_bf;

	//直接复用左右单旋
	RotateL(subL);
	RotateR(parent);

	if (bf == 0)
	{
		parent->_bf = 0;
		subL->_bf = 0;
		subLR->_bf = 0;
	}
	else if (bf == 1)
	{
		parent->_bf = 0;
		subL->_bf = -1;
		subLR->_bf = 0;
	}
	else if (bf == -1)
	{
		parent->_bf = 1;
		subL->_bf = 0;
		subLR->_bf = 0;
	}
	else
	{
		assert(false);
	}
}
        右左双旋:

        右左双选的步骤与左右双选的步骤一模一样,只是方向反了。这里就不画图演示了,具体步骤为:1.右旋subR  2.左旋parent  3.记subRL旋转前的平衡因子,判断插入的位置,根据插入的位置来更新平衡因子

代码语言:javascript
复制
//右左双旋
void RotateRL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	int bf = subRL->_bf;

	//直接复用右左单旋
	RotateR(subR);
	RotateL(parent);

	if (bf == 0)
	{
		parent->_bf = 0;
		subR->_bf = 0;
		subRL->_bf = 0;
	}
	else if (bf == 1)
	{
		parent->_bf = -1;
		subR->_bf = 0;
		subRL->_bf = 0;
	}
	else if (bf == -1)
	{
		parent->_bf = 0;
		subR->_bf = 1;
		subRL->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

AVL的搜索

        AVL的搜索与搜索二叉树的搜索无异,这里我们直接给出

代码语言:javascript
复制
Node* Find(const K& key)
{
    Node* cur = _root;
    while (cur)
    {
        if (cur->_kv.first < key)
        {
            cur = cur->_right;
        }
        else if (cur->_kv.first > key)
        {
            cur = cur->_left;
        }
        else
        {
            return cur;
        }
    }
    return nullptr;
}

总结

        完整代码如下

代码语言:javascript
复制
#pragma once
#include<iostream>
#include<assert.h>
#include<vector>
using namespace std;

template<class K, class V>
struct AVLTreeNode
{
	// 需要parent指针,后续更新平衡因子可以看到 
	pair<K, V> _kv;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	int _bf; // balance factor 平衡因子

	AVLTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _bf(0)
	{}
};

template<class k, class v>
class AVLTree
{

public:
	//typedef AVLTreeNode<k, v> Node;
	using Node = AVLTreeNode<k, v>;

	bool Insert(const pair<k,v>& kv)
	{
		Node* cur = _root;
		Node* parent = cur;

		if (cur == nullptr)
		{
			_root = new Node(kv);
			return true;
		}
		
		while(cur)
		{
			if (cur->_kv.first > kv.first)       //pair重载了大小于运算符
			{
				parent = cur;
				cur = cur->_left;
				
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
					
			}
			else
				return false;
		}
		if (parent->_kv.first > kv.first)
		{
			cur = new Node(kv);
			parent->_left = cur;
		}
		else
		{
			cur = new Node(kv);
			parent->_right = cur;
		}
		
		//连接到父亲节点
		cur->_parent = parent;


		//控制平衡
		//更新平衡因子
		while (parent)
		{
			if (parent->_left == cur)
				parent->_bf--;
			else
				parent->_bf++;

			if (parent->_bf == 0)
			{
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				//进行旋转
		
				if (parent->_bf == -2 && cur->_bf == -1)//右单旋(单纯的左边高)
				{
					RotateR(parent);
				}
				else if(parent->_bf==2&&cur->_bf==1)//左单选(单纯的右边高)
				{
					RotateL(parent);
				}
				else if (parent->_bf==-2&&cur->_bf==1)//左右双旋:先左单旋再右单旋(不单纯的左边高)
				{
					RotateLR(parent);
				}
				else if (parent->_bf==2&&cur->_bf==-1)//右左双旋:先右单旋再左单旋(不单纯的右边高)
				{
					RotateRL(parent);
				}
				else
				{
					assert(false);
				}

				break;
			}
			else
			{
				assert(false);
				//预防未知错误的编程逻辑
			}
		}


		return true;
	}

	//右单旋
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		Node* pParent = parent->_parent;
		subL->_right = parent;
		parent->_parent = subL;

		parent->_left = subLR;
		if(subLR)
			subLR->_parent = parent;

		//判断:若旋转的节点为根,则代表这是一颗完整的树,不需要处理
		//若旋转的节点不为根,则代表这是一颗子树,需要连接至上面的树
		if (pParent == nullptr)
		{
			_root=subL;
			subL->_parent = nullptr;
			
		}
		else
		{
			if (pParent->_left == parent)
			{
				pParent->_left = subL;
				subL->_parent = pParent;
			}
			else
			{
				pParent->_right = subL;
				subL->_parent = pParent;
			}
		}

		//更新平衡因子
		subL->_bf = parent->_bf = 0;

	}

	//左单旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		
		parent->_right = subRL;
		if(subRL)
			subRL->_parent = parent;

		Node* pParent = parent->_parent;
		subR->_left = parent;
		parent->_parent = subR;

		if (pParent == nullptr)
		{
			_root = subR;
			subR->_parent= nullptr;
		}
		else
		{
			if (pParent->_left == parent)
			{
				pParent->_left = subR;
			}
			else
			{
				pParent->_right = subR;
			}
			subR->_parent = pParent;
		}

		//更新平衡因子
		subR->_bf = parent->_bf = 0;

	}

	//左右双旋
	void RotateLR(Node* parent)
	{
		//提前记录,插入的位置
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;

		//直接复用左右单旋
		RotateL(subL);
		RotateR(parent);

		if (bf == 0)
		{
			parent->_bf = 0;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else if (bf == 1)
		{
			parent->_bf = 0;
			subL->_bf = -1;
			subLR->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 1;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}


	//右左双旋
	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;

		//直接复用右左单旋
		RotateR(subR);
		RotateL(parent);

		if (bf == 0)
		{
			parent->_bf = 0;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else if (bf == 1)
		{
			parent->_bf = -1;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			subR->_bf = 1;
			subRL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	
	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < key)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}

private:


	Node* _root = nullptr;
};

如有纰漏还请各位大佬斧正

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2026-01-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概念
  • 平衡因子的更新
    • 停止更新条件
  • 实现
    • AVL树结构的实现
    • AVL树的插入
      • 单旋
      • 双旋 
    • AVL的搜索
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档