数据结构中的平衡搜索树 --- AVL树是怎样进行旋转处理的?(平衡因子版本)

目录

前言

        搜索二叉树

AVL树

节点的定义

插入

旋转


前言

搜索二叉树

搜索二叉树 又称 二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树

示例:

具体可以查看搜索二叉树

         但是二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树(或接近单只树),二叉搜索树的性能就失去了,并且 时间复杂度会退化成O(N),因此需要对底层结构进行平衡处理,即采用平衡树(AVL树、红黑树)来实现,使二叉搜索树的性能都能达到最优.

AVL树

AVL树的概念

        二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查 找元素相当于在顺序表中搜索元素,效率低下 。因此,两位俄罗斯的数学家 G.M.Adelson-Velskii和E.M.Landis 1962 年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右 子树高度之差的绝对值不超过 1( 需要对树中的结点进行调整 ) ,即可降低树的高度,从而减少平均搜索长度。

一棵 AVL 树或者是空树,或者是具有以下性质的二叉搜索树:
  • 它的左右子树都是AVL
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1) --- 一般是右子树减去左子树等于根

如果一棵二叉搜索树是高度平衡的,它就是 AVL 树。如果它有 n 个结点,其高度可保持在 $O(log_2 n)$ ,搜索时间复杂度 O($log_2 n$)

AVL树节点的定义
template<class K, class V>
class AVLTreeNode
{
	AVLTreeNode<K,V>* _left;	//左子树节点
	AVLTreeNode<K, V>* _right;	//右子树节点
	AVLTreeNode<K, V>* _parent;	//父节点
	pair<K, V> _kv;
	int _bf; //balance factor :平衡因子

	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		,_kv(kv)
		,_bf(0)
	{}
};
AVL树的插入
AVL 树就是在二叉搜索树的基础上引入了平衡因子,因此 AVL 树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:
  • 按照二叉搜索树的方式插入新节点
  •  调整节点的平衡因子
template<class K,class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	bool insert(const pair<K, V>& kv)
	{
		//_root为空
		if (_root == nullptr) 
		{
			_root = new Node(kv);
			return true;
		}

		//_root不为空
		Node* cur = _root;
		Node* parent = nullptr;   //记录cur的父节点,方便进行链接

		while (cur)
		{
			if (kv.first < cur->_kv.first)  //插入的值小于存储的值
			{
				parent = cur;
				cur = cur->_left;
			}
			else if(kv.first > cur->_kv.first) //插入的值大于存储的值
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false; //相等,则插入失败
			}
        }

			//当前位置为空,插入的值与原本的值不相等,进行链接
			cur = new Node(kv);
			if (kv.first < parent->_kv.first)
			{
				parent->_left = cur;
			}
			else
			{
				parent->_right = cur;
			}

			cur->_parent = parent;  //需要注意,进行链接


			//链接之后,此时需要更新平衡因子

			//.......

			return true;
		
		
	}

private:
	Node* _root = nullptr;

};

此时怎样调整节点的平衡因子呢?

观察一下平衡因子的性质: 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)

                                                                        而且平时我们习惯使用右子树高度减去左子树高度等于根

可以得出:如果新增节点是右子树,那么父节点需要++;如果新增节点是左子树,那么父节点需要 --

cur = parent->_right;     parent->_bf++;

cur = parent->_left;       parent->_bf--;

示例1:

示例2:

那么此时产生的新问题是,当父节点更新后,要不要继续向上更新?或者什么决定了要不要继续向上更新???

观察示例1与示例2可以得出,如果parent节点的高度发生了变化,那么是需要继续更新的,如果parent的高度没有发生变化,那么就不需要继续更新。

  • 情况1:parent->_bf == 1 || parent->_bf == -1      ---    需要继续向上更新,因为说明插入之前 parent->_bf == 0  ,表示插入之前父节点两边的高度相等,现在有一边插入了一个新节点,此时高度发生了改变,所以需要继续向上更新

  • 情况2:parent->_bf == 0     ---    不用向上更新,因为说明插入之前 parent->_bf == 1 || parent->_bf == -1,表示插入之前父节点两边的高度不相等,现在矮的一边插入了一个新节点,此时高度平衡,所以不用向上更新。

  • 情况3:parent->_bf == 2 || parent->_bf == -2   ---   所在子树高度不平衡,需要进行旋转处理
template<class K,class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	bool insert(const pair<K, V>& kv)
	{
		//_root为空
		if (_root == nullptr) 
		{
			_root = new Node(kv);
			return true;
		}

		//_root不为空
		Node* cur = _root;
		Node* parent = nullptr;   //记录cur的父节点,方便进行链接

		while (cur)
		{
			if (kv.first < cur->_kv.first)  //插入的值小于存储的值
			{
				parent = cur;
				cur = cur->_left;
			}
			else if(kv.first > cur->_kv.first) //插入的值大于存储的值
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false; //相等,则插入失败
			}
         }

			//当前位置为空,插入的值与原本的值不相等,进行链接
			cur = new Node(kv);
			if (kv.first < parent->_kv.first)
			{
				parent->_left = cur;
			}
			else
			{
				parent->_right = cur;
			}

			cur->_parent = parent;  //需要注意,进行链接


			//链接之后,此时需要更新平衡因子

			while (parent)
			{
				if (cur == parent->_right)
				{
					parent->_bf++;
				}
				else
				{
					parent->_bf--;
				}

				if (parent->_bf == 0)
				{
					break; 
				}
				else if (parent->_bf == 1 || parent->_bf == -1)
				{
					//继续向上更新
					parent = parent->_parent;
					cur = cur->_parent;
				}
				else if (parent->_bf == 2 || parent->_bf == -2)
				{
					//需要进行旋转处理
					//........
				}
				else
				{
					//程序走到这里说明问题很严重,直接断言
					assert(false);
				}

			}

			return true;
		
	}

private:
	Node* _root = nullptr;
};

那么什么情况下会出现旋转处理???

1.更新平衡因子:如果更新完成,平衡因子没有出现问题 | _bf l <= 1,平衡结构没有受到影响,不需要处理
 

2.更新平衡因子:如果更新完成,平衡因子出现问题 | _bf | > 1,平衡结构受到影响,需要处理(旋转)

AVL树的旋转

   如果在一棵原本是平衡的AVL 树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。所以旋转的本质有两点:
  • 1.让这棵子树平衡
  • 2.降低这棵子树的高度
根据节点插入位置的不同,AVL 树的旋转分为四种:

1.新节点插入较高右子树的右侧--- 右右:左单旋
抽象图:

旋转的过程:
  • ① b变成了30的右边
  • ② 30变成60的左边
  • ③ 60变成整棵树的根
     
在旋转过程中,有以下几种情况需要考虑:
  • ① 60节点的左孩子可能存在,也可能不存在
  • ② 30可能是根节点,也可能是子树

如果是根节点,旋转完成后,要更新根节点

 如果是子树,可能是某个节点的左子树,也可能是右子树

具象图:

当h == 0:

当h == 1:

当h == 2:
 

示例:

变量定义:

代码:
	//左单旋
	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;

		//值得注意的是,parent节点不一定为根节点,也就是旋转的可能是一棵子树而不是整棵树
		if (pparent == nullptr) //意味着parent节点是根节点
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			//判断parent 在 祖先节点的左还是右
			if (pparent->_right == parent)
			{
				pparent->_right = subR;
			}
			else
			{
				pparent->_left = subR;
			}

			subR->_parent = pparent;  //更改subR的父节点
		}

		//注意:一定要更新平衡因子
		parent->_bf = subR->_bf = 0;
	}

2.新节点插入较高左子树的左侧 -- 左左:右单旋 (可参考左单旋)
抽象图:

代码:

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

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

		//提前记录祖先节点
		Node* pparent = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		if (parent == _root) 
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			//判断parent 在 祖先节点的左还是右
			if (pparent->_right == parent)
			{
				pparent->_right = subL;
			}
			else
			{
				pparent->_left = subL;
			}

			subL->_parent = pparent;  //更改subR的父节点
		}

		//注意:一定要更新平衡因子
		parent->_bf = subL->_bf = 0;
	}

3. 新节点插入较高左子树的右侧 --- 左右:先左单旋再右单旋
抽象图:
代码演示:
	void RotateLR(Node* parent)
	{
		RotateL(parent->_left);  //左旋
		RotateR(parent);		 //右旋
	}

这样可以嘛?其实有个非常严重的错误,就是无论左旋还是右旋函数最后都会把parent ,subR,subL的平衡因子置成0

        以上面的图为例(新增节点在subLR的左子树):第一次单旋会把30节点 、60节点 的平衡因子置成 0,第二次单旋会把60节点 、90节点 的平衡因子置成 0 ,这显然是不对的,因为90节点最后的平衡因子应该是1。所以需要分情况讨论:

新增节点在subLR的右子树

一般来说最后一种不需要考虑,因为都会被单旋修改为0,但是建议不要依赖单旋

总结这里不能依靠左旋or右旋函数来修改平衡因子,需要手动修改

代码如下:

//左右双旋
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		//因为双旋过后 _bf 都会被修改为0,所以需要提前记录
		int bf = subLR->_bf;

		RotateL(parent->_left);  //左旋
		RotateR(parent);		 //右旋

		if (bf == 0)
		{
			parent->_bf = 0;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 1;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else if (bf == 1)
		{
			parent->_bf = 0;
			subL->_bf = -1;
			subLR->_bf = 0;
		}
		else
		{
			assert(false);  //依旧直接断言,走到这里说明程序出现严重错误
		}

	}

4. 新节点插入较高右子树的左侧 --- 右左:先右单旋再左单旋

(参考左右双旋)

抽象图:

代码:

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

		int bf = subRL->_bf;

		RotateR(parent->_right);  
		RotateL(parent);		 

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

那么什么时候左旋?什么时候右旋,什么时候双旋呢???

观察上面4种旋转的情况可以知道:

  • 左子树高 - 右旋 
  • 右子树高 - 左旋   
  • 左子树高,左子树的右孩子高 - 左右双旋
  • 右子树高,右子树的左孩子高 - 右左双旋

 AVL 树的删除
        因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,只不错与删除不同的时,删除节点后的平衡因子更新,最差情况下一直要调整到根节点的位置。
AVL 树的性能
        AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过 1 ,这样可以保证查询时高效的时间复杂度,即$log_2 (N)$ 。但是如果要对 AVL 树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的( 即不会改变 ) ,可以考虑 AVL 树,但一个结构经常修改,就不太适合。

最后附上完整代码以及测试一棵树是否是AVL树的方法:

                                                                                                                AVLTree.h

#pragma once
#include <iostream>
#include <assert.h>
#include <string>

using namespace std;

template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode<K,V>* _left;	//左子树节点
	AVLTreeNode<K, V>* _right;	//右子树节点
	AVLTreeNode<K, V>* _parent;	//父节点
	pair<K, V> _kv;
	int _bf; //balance factor :平衡因子

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

template<class K,class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	void InOrder()
	{
		_Inorder(_root);
		cout << endl;
	}

	bool Insert(const pair<K, V>& kv)
	{
		//_root为空
		if (_root == nullptr) 
		{
			_root = new Node(kv);
			return true;
		}

		//_root不为空
		Node* cur = _root;
		Node* parent = nullptr;   //记录cur的父节点,方便进行链接

		while (cur)
		{
			if (kv.first < cur->_kv.first)  //插入的值小于存储的值
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kv.first > cur->_kv.first) //插入的值大于存储的值
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false; //相等,则插入失败
			}

		}

		//当前位置为空,插入的值与原本的值不相等,进行链接
		cur = new Node(kv);
		if (kv.first < parent->_kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}

		cur->_parent = parent;  //需要注意,进行链接


		//链接之后,此时需要更新平衡因子

		while (parent)
		{
			if (cur == parent->_right)
			{
				parent->_bf++;
			}
			else
			{
				parent->_bf--;
			}

			if (parent->_bf == 0)
			{
				break; 
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				//继续向上更新
				parent = parent->_parent;
				cur = cur->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				//需要进行旋转处理 --- 1.降低子树的高度  2.继续保持平衡
				if (parent->_bf == 2 && cur->_bf == 1)
				{
					//左旋
					RotateL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == -1)
				{
					//右旋
					RotateR(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;
	}


	//判断是否为AVL树
	bool IsBalance()
	{
		return _IsBalance(_root);
	}

		

protected:
	void _Inorder(Node* root)
	{
		if (root == nullptr)
			return;

		_Inorder(root->_left);
		cout << root->_kv.first << " ";
		_Inorder(root->_right);
	}


	//左单旋
	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;

		//值得注意的是,parent节点不一定为根节点,也就是旋转的可能是一棵子树而不是整棵树
		if (pparent == nullptr) //意味着parent节点是根节点
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			//判断parent 在 祖先节点的左还是右
			if (pparent->_right == parent)
			{
				pparent->_right = subR;
			}
			else
			{
				pparent->_left = subR;
			}

			subR->_parent = pparent;  //更改subR的父节点
		}

		//注意:一定要更新平衡因子
		parent->_bf = subR->_bf = 0;
	}

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

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

		//提前记录祖先节点
		Node* pparent = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		if (parent == _root) 
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			//判断parent 在 祖先节点的左还是右
			if (pparent->_right == parent)
			{
				pparent->_right = subL;
			}
			else
			{
				pparent->_left = subL;
			}

			subL->_parent = pparent;  //更改subR的父节点
		}

		//注意:一定要更新平衡因子
		parent->_bf = subL->_bf = 0;
	}



	//左右双旋
	void RotateLR(Node* parent)
	{
		//记录修改平衡因子的位置
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		//因为双旋过后bf都会被修改为0,所以需要提前记录subLR的平衡因子
		int bf = subLR->_bf;

		RotateL(parent->_left);
		RotateR(parent);

		//分三种情况
		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 if (bf == 0)
		{
			parent->_bf = 0;
			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(parent->_right);  //右旋
		RotateL(parent);		  //左旋

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

	//计算高度
	int _Height(Node* root)
	{
		if (root == nullptr)
			return 0;

		int leftH = _Height(root->_left);  //左子树高度
		int rightH = _Height(root->_right); //右子树高度

		return leftH > rightH ? leftH + 1 : rightH + 1;  //返回的是整棵树的高度
	}

	//判断是否是AVL树 - 子函数
	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
			return true;

		int left_h = _Height(root->_left);
		int right_h = _Height(root->_right);

		//检查平衡因子
		if (right_h - left_h != root->_bf)
		{
			cout << root->_kv.first << "节点平衡因子异常" << endl;
			return false;
		}

		//判断左右子树之间的差是否 < 2  abs:求绝对值
		return abs(left_h - right_h) < 2
			&& _IsBalance(root->_left)
			&& _IsBalance(root->_right);
	}

	
protected:
	Node* _root = nullptr;
};



void Test_AVLTree1()
{
	int arr1[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	int arr2[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };

	AVLTree<int, int> t1;

	for (auto e : arr1)
	{
		t1.Insert(make_pair(e, e));
		cout << e << "插入:" << t1.IsBalance() << endl;  //插入进行检查
	}

	t1.InOrder();
	cout << t1.IsBalance() << endl;
}

  • 9
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值