当前位置: 首页 > news >正文

34.二叉树进阶3(平衡二叉搜索树 - AVL树及其旋转操作图解)

⭐上篇文章:34.二叉树进阶3(C++STL 关联式容器,set/map的介绍与使用)-CSDN博客

⭐本篇代码:c++学习/19.map和set的使用用与模拟 · 橘子真甜/c++-learning-of-yzc - 码云 - 开源中国 (gitee.com)

⭐标⭐是比较重要的部分

一. 二叉搜索树的缺点

        之前文章中提到,普通的二叉搜索树在某些情况下会退出成链表,或者根节点的左右子树的高度差非常大。这个时候就会导致其搜索效率由 O(logN) -> O(N)。

        为了解决这个问题,计算机科学家提供了AVL树。AVL树一种平衡二叉搜索树,通过平衡因子和旋转操作来保证二叉搜索树是平衡的。

二. AVL树及其特点

1 AVL树是一颗二叉搜索树,满足二叉搜索树的性质

2 AVL树每一个节点中有一个平衡因子,表示该节点左右子树的高度差的绝对值,且该值不能超过1(即左右子树高度差不能超过1)

        可以看到,通过平衡因子可以保证AVL树是高度平衡的!AVL树在每一次插入新节点之后都要检验该节点和其父节点的平衡因子,一旦检测到平衡因子的值超过1,就要通过旋转操作来调整平衡因子。

三. AVL树的节点和旋转操作图解

3.1 AVL树节点

//节点
template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;//节点的双亲,用于更新平衡因子

	int _bf;//平衡因子 balance factor 
	pair<K, V> _kv;

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

二叉树一般都是存储键值对<k,v>。AVL树需要一个平衡因子bf

3.2 AVL树结构 

template<class K, class V>
class AVlLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	bool Insert(const pair<K, V>& kv)
	{}
private:
	Node* _root;
};

3.3 AVL树的插入操作 ⭐

        AVL树的插入操作和二叉搜索树几乎一样,不过在插入节点之后要根据平衡因子才调整整棵树的结构。

a 左单旋

        如果一个节点的平衡因子为2,即右子树比左子树高2则需要对该节点进行左单旋

代码如下:


	//右右,左单旋.一共需要调整6条线,四个节点
	void RotateLeft(Node* parent)
	{
		if (!parent)
			return;
		Node* ppNode = parent->_parent;//要旋转节点的父亲
		Node* SubR = parent->_right;//要旋转节点的右孩子
		Node* SubRLeft = SubR->_left;//要旋转节点右孩子的左孩子
		

		//一:调整节点
		parent->_right = SubRLeft;
		if (SubRLeft)
			SubRLeft->_parent = parent;
		
		SubR->_left = parent;
		parent->_parent = SubR;

		//1.parent是根,现在SubR是根
		//2.parent是整棵树的子树,找到其父亲,旋转完成后,让subR与其父亲相连接
		if (_root == parent)
		{
			_root = SubR;
			SubR->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = SubR;
			}
			else
			{
				ppNode->_right = SubR;
			}

			SubR->_parent = ppNode;
		}

		//二:更新平衡因子
		parent->_bf = SubR->_bf = 0;
		
	}

b 右单旋 

        如果一个节点的左子树比右子树高2,则需要对该节点进行右单旋

代码如下:


	//左左,右单旋
	void RotateRight(Node* parent)
	{
		if (!parent)
			return;
		Node* ppNode = parent->_parent;
		Node* SubL = parent->_left;
		Node* SubLRight = SubL->_right;

		//1.旋转,调整节点
		parent->_left = SubLRight;
		if (SubLRight)
			SubLRight->_parent = parent;

		SubL->_right = parent;
		parent->_parent = SubL;

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

		//2.更新平衡因子
		//旋转之后,subL为子树根,左子树高度为h+1,右子树为h+parent高度1.左右子树高度一样,平衡因子为0
		//parent左右子树高度也相等,平衡因子为0
		parent->_bf = SubL->_bf = 0;
	}

c 左双旋

        如果新增节点在较高右子树的左侧,则需要两次旋转。如下图

双旋可以复用单旋的代码


	//右左
	void RotateRightLeft(Node* parent)
	{
		//注意控制三个节点的平衡因子
		Node* SubR = parent->_right;
		Node* SubRL = SubR->_left;
		int bf = SubRL->_bf;

		RotateRight(parent->_right);    //上图旋转10
		RotateLeft(parent);             //上图旋转5

		//对应图来理解平衡因子
		if (bf == -1)//右左节点的左边插入
		{
			parent->_bf = 0;
			SubR->_bf = 1;
		}
		else if (bf == 1)//右左节点的右边插入
		{
			SubR->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == 0)//此时SubRL就是新增节点
		{
			parent->_bf = 0;
			SubR->_bf = 0;
		}
		//此时右左节点是根节点
		SubRL->_bf = 0;
	}

 c 右双旋

        如果新增节点在较高左子树的右侧,则需要两次旋转。如下图


	//左右,先左旋parent->left,再右旋parent
	void RotateLeftRight(Node* parent)
	{
		Node* SubL = parent->_left;
		Node* SubLR = SubL->_right;
		int bf = SubLR->_bf;

		RotateLeft(SubL);    //上图旋转5
		RotateRight(parent); //上图旋转10
        
        //更新平衡因子
		if (bf == 1)
		{
			parent->_bf = 0;
			SubL->_bf = -1;
		}
		else if (bf == -1)
		{
			SubL->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == 0)
		{
			SubL->_bf = 0;
			parent->_bf = 0;
		}

		//左右节点为根节点
		SubLR->_bf = 0;
	}

注意,每一次旋转之后都要更新平衡因子

四. 二叉树插入完整代码

#pragma once
#include<iostream>
#include<queue>
#include<string>
using namespace std;

//AVL树		(高度平衡二叉搜索树)
//1.是二叉搜索树
//2.树及所有子树都要满足左右左右子树的高度差不超过1

//为了方便实现,我们在这里引入了平衡因子的概念,其值范围只能是0,1,-1(平衡因子不是必须的)
//平衡因子=右子树的高度-左子树的高度
//这样就能够控制其高度位O(logN)

//节点
template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;//节点的双亲,用于更新平衡因子

	int _bf;//平衡因子 balance factor 
	pair<K, V> _kv;

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

//AVLTree
//1.按照搜索树的规则插入
//2.更新平衡因子
//3.如果没有出现违规的平衡因子,插入结束
//4.如果有存在违规的平衡因子,需要旋转
template<class K, class V>
class AVlLTree
{
	typedef AVLTreeNode<K, V> Node;
public:


	//一个节点的插入,只会影响其祖先的平衡因子,所以只要判断其祖先的平衡因子即可
	//1.如果cur是其父亲的左,parent->_bf--.如果cur是其父亲的右,parent->_bf++
	//2.在这些祖先中,更新完,parent->_bf==0,说明parent的高度不变,更新结束,插入完成(把这颗parent为根的子树矮的填上了)
	//3.更新完parent,parent->_bf==1 or -1,parent高度变了,继续往上更新(说明更新前parent->_bf==0,parent的高度变了)\
	//4.如果更新完parent的bf,parent->_bf==2, or -2,parent的所在子树不平衡,需要旋转处理,旋转后插入结束

	bool Insert(const pair<K, V>& kv)
	{
		//1.先按搜索树进行插入
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(kv);
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
			cur->_parent = parent;//要将cur与其父亲链接
		}
		else
		{
			parent->_right = cur;
			cur->_parent = parent;
		}

		//2.从插入节点开始对这个节点及所有祖先节点更新平衡因子
		while (parent)
		{
			if (parent->_right == cur)
			{
				parent->_bf++;
			}
			else
			{
				parent->_bf--;
			}

			if (parent->_bf == 0)
			{
				//parent所在子树高度不变,更新结束
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				//parent所在子树高度变了,有0变1或者-1,此时继续向上更新
				cur = parent;
				parent = parent->_parent;
			}
			else //if (parent->_bf == 2 || parent->_bf == -2)
			{
				//parent所在子树出现问题,需要旋转处理
				//1.旋转完成后,它还得是完整的搜索树
				//2.旋转完成后,它要变平衡

				//旋转方法
				if (parent->_bf == 2)//左旋
				{
					if (cur->_bf == 1)//右右,直接对parent左单旋
					{
						//1.左单旋
						//将subR的左边给parent的右边(parent的右边指向subR的左边)
						//将parent变为subR的左边(subR的左边指向parent)
						RotateLeft(parent);
					}
					else if (cur->_bf == -1)//右左,先对parent->right右单旋,再对parent左单旋
					{
						//左双旋
						//先右单旋cur,让parent变为右右
						//再左单旋parent
						RotateRightLeft(parent);
					} 
				}
				else if (parent->_bf == -2)
				{	
					if (cur->_bf == -1)//左左,直接对parent右单旋
					{
						//右单旋
						RotateRight(parent);
					}
					else if (cur->_bf == 1)//左右,对parent->left左单旋,再对parent右单旋
					{
						//右双旋
						//先左单旋cur,让parenttt变为左左
						//再右单旋parent
						//此时parent左右这个节点为根
						RotateLeftRight(parent);
					}
				}
				//旋转完成后之间跳出即可,这是由于旋转让我平衡了,高度恢复到了插入新节点之前的高度(即高度不会变化)
				//如果是子树,对上层节点不会有影响。更新结束,跳出即可
				break;			
			}

		}
		return true;
	}

	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
private:

	//右右,左单旋.一共需要调整6条线,四个节点
	void RotateLeft(Node* parent)
	{
		if (!parent)
			return;
		Node* ppNode = parent->_parent;//要旋转节点的父亲
		Node* SubR = parent->_right;//要旋转节点的右孩子
		Node* SubRLeft = SubR->_left;//要旋转节点右孩子的左孩子
		

		//一:调整节点
		parent->_right = SubRLeft;
		if (SubRLeft)
			SubRLeft->_parent = parent;
		
		SubR->_left = parent;
		parent->_parent = SubR;

		//1.parent是根,现在SubR是根
		//2.parent是整棵树的子树,找到其父亲,旋转完成后,让subR与其父亲相连接
		if (_root == parent)
		{
			_root = SubR;
			SubR->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = SubR;
			}
			else
			{
				ppNode->_right = SubR;
			}

			SubR->_parent = ppNode;
		}

		//二:更新平衡因子
		parent->_bf = SubR->_bf = 0;
		
	}

	//左左,右单旋
	void RotateRight(Node* parent)
	{
		if (!parent)
			return;
		Node* ppNode = parent->_parent;
		Node* SubL = parent->_left;
		Node* SubLRight = SubL->_right;

		//1.旋转,调整节点
		parent->_left = SubLRight;
		if (SubLRight)
			SubLRight->_parent = parent;

		SubL->_right = parent;
		parent->_parent = SubL;

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

		//2.更新平衡因子
		//旋转之后,subL为子树根,左子树高度为h+1,右子树为h+parent高度1.左右子树高度一样,平衡因子为0
		//parent左右子树高度也相等,平衡因子为0
		parent->_bf = SubL->_bf = 0;
	}

	//右左
	void RotateRightLeft(Node* parent)
	{
		//注意控制三个节点的平衡因子
		Node* SubR = parent->_right;
		Node* SubRL = SubR->_left;
		int bf = SubRL->_bf;

		RotateRight(parent->_right);
		RotateLeft(parent);

		//对应图来理解平衡因子
		if (bf == -1)//右左节点的左边插入
		{
			parent->_bf = 0;
			SubR->_bf = 1;
		}
		else if (bf == 1)//右左节点的右边插入
		{
			SubR->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == 0)//此时SubRL就是新增节点
		{
			parent->_bf = 0;
			SubR->_bf = 0;
		}
		//此时右左节点是根节点
		SubRL->_bf = 0;
	}

	//左右,先左旋parent->left,再右旋parent
	void RotateLeftRight(Node* parent)
	{
		Node* SubL = parent->_left;
		Node* SubLR = SubL->_right;
		int bf = SubLR->_bf;

		RotateLeft(SubL);
		RotateRight(parent);

		if (bf == 1)
		{
			parent->_bf = 0;
			SubL->_bf = -1;
		}
		else if (bf == -1)
		{
			SubL->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == 0)
		{
			SubL->_bf = 0;
			parent->_bf = 0;
		}

		//左右节点为根节点
		SubLR->_bf = 0;
	}

	//中序遍历辅助函数
	void _InOrder(Node* root)
	{
		if (!root)
			return;
		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << " 平衡因子:" << root->_bf << endl;
		_InOrder(root->_right);
	}

	Node* _root;
};

相关文章:

  • Flask框架中局部刷新页面
  • 北斗短报文+5G:遨游通信终端开启全域智能物联新时代
  • RAG技术的PDF智能问答系统
  • Effective Python:(18)作用域问题
  • 番外篇 - Docker的使用
  • VSCode 配置优化指南
  • 【从零开始学习计算机科学】硬件设计与FPGA原理
  • r1-reasoning-rag:一种新的 RAG 思路
  • RtlLookupAtomInAtomTable函数分析之RtlpAtomMapAtomToHandleEntry函数的作用是验证其正确性
  • 【ArcGIS】地理坐标系
  • 空间域与频域图像处理
  • 基于DeepSeek的智慧医药系统(源码+部署教程)
  • C语言单链表头插法
  • 嘉立创:电子产业革新背后的隐形巨擘
  • C语言——【全局变量和局部变量】
  • 【智能体】本地安装Conda和搭建OpenManus环境
  • 人机共创:AI与人类编剧如何携手打造电影新纪元
  • 蓝桥备赛(13)- 链表和 list(上)
  • 第五章:go 的数据类型 及go语言拼接字符串有哪些方式
  • 百度移动生态事业群聚焦UGC战略,贴吧迎新调整
  • 像wordpress一样的网站/个人博客网站设计毕业论文
  • 宽带动态ip如何做网站访问/百度极速版app下载安装挣钱
  • 淄博网站制作企业营销/2023新闻摘抄大全
  • 平远县建设工程交易中心网站/seo岗位工作内容
  • 成都网站的优化/重庆seo网站建设
  • 奇胜网络 邯郸网站建设/网页制作培训网站