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树节点

cpp 复制代码
//节点
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树结构

cpp 复制代码
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则需要对该节点进行左单旋

代码如下:

cpp 复制代码
	//右右,左单旋.一共需要调整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,则需要对该节点进行右单旋

代码如下:

cpp 复制代码
	//左左,右单旋
	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 左双旋

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

双旋可以复用单旋的代码

cpp 复制代码
	//右左
	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 右双旋

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

cpp 复制代码
	//左右,先左旋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;
	}

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

四. 二叉树插入完整代码

cpp 复制代码
#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;
};
相关推荐
Dream it possible!4 小时前
LeetCode 热题 100_字符串解码(71_394_中等_C++)(栈)
c++·算法·leetcode
My Li.5 小时前
c++的介绍
开发语言·c++
开心比对错重要5 小时前
leetcode69.x 的平方根
数据结构·算法·leetcode
Doopny@6 小时前
数字组合(信息学奥赛一本通-1291)
数据结构·算法·动态规划
君莫愁。6 小时前
【Unity】搭建基于字典(Dictionary)和泛型列表(List)的音频系统
数据结构·unity·c#·游戏引擎·音频
邪恶的贝利亚7 小时前
C++之序列容器(vector,list,dueqe)
开发语言·c++
原来是猿7 小时前
蓝桥备赛(13)- 链表和 list(上)
开发语言·数据结构·c++·算法·链表·list
成功助力英语中国话7 小时前
SDK编程,MFC编程,WTL编程之间的关系
c++·mfc
仟濹7 小时前
【算法 C/C++】二维差分
c语言·c++·算法
总斯霖8 小时前
题解:士兵排列
数据结构·c++·算法