平衡二叉搜索树/AVL树

AVL树的特性

  1. 左右子树高度差的绝对值不超过1。(即左右子树高度差取值为-1,0,1)
  2. 且左右子树均为AVL树
  3. 右子树的值大于左子树的值

在搜索二叉树中我们提及了搜索二叉树的退化问题。

当有序(升序或降序)地插入时,搜索二叉树就会出现下图的情况,搜索次数从树的高度次退化成n次。而平衡搜索二叉树就解决了这一问题。

 

平衡方法

在解决方案中,左右子树的差值被定为平衡因子,所以上述例子用平衡二叉搜索树的角度来看如下图。此时出现了不被允许的2和-2,意味着平衡被打破,需要进行调整平衡。

插入时平衡因子的变化逻辑

以下绿色方框代表一棵子树,h为其高度,且h>=0,具体节点上方的数字为平衡因子。

因为插入节点,导致60这个节点的左右子树失衡了。

 

        如图我们可以看到,根据插入位置的不同,其父节点的平衡因子的变化也不同。因为此处我使用的平衡因子 = 右子树高度 - 左子树高度。所以当插入到右子树时,父节点平衡因子+1,插入到左子树时,父节点平衡因子-1。

了解完插入时平衡因子的变化,我们再来了解一下变化的两种情况。

(1)插入后父节点平衡因子变为1或-1的,那么其原来的平衡因子是0,意味着该父节点原本平衡,插入节点后打破了平衡使该父节点为根的整棵子树高度产生变化,此时平衡因子需要继续向上更新,于是60这个节点也受其影响改变了,此时出现了不被允许的失衡,此时就要旋转调整平衡。

(2)第二种情况比较简单,插入后父节点平衡因子变为0,意味着其原本有一些失衡,但还在允许范围内,但插入后完全平衡了,此时无需再继续更新,因为高度没有改变。

总结:亚健康(-1、1)到健康(0)不用管,反之健康到亚健康要去向上更新平衡因子,因为其父节点可能直接就生病了。

// 在AVL树中插入值为data的节点
		bool Insert(const T& data)
		{
			if (_pRoot == nullptr)
			{
				_pRoot = new Node(data);
				return true;
			}
			Node* parent = nullptr;
			Node* cur = _pRoot;

			while (cur)
			{
				if (data > cur->_data)
				{
					parent = cur;
					cur = cur->_pRight;
				}
				else if (data < cur->_data)
				{
					parent = cur;
					cur = cur->_pLeft;
				}
				else
				{
					return false;
				}
			}

			cur = new Node(data);
			if (parent->_data < data)
			{
				parent->_pRight = cur;
			}
			else
			{
				parent->_pLeft = cur;
			}
			cur->_pParent = parent;


			//调整平衡因子
			while (parent)
			{
				if (cur == parent->_pLeft)
				{
					parent->_bf--;
				}
				else
				{
					parent->_bf++;
				}

				//插入后恰好平衡了,跳出循环
				if (parent->_bf == 0)
				{
					break;
				}    //高度出现变化但还未失衡,向上走进行判断
				else if (parent->_bf == 1 || parent->_bf == -1)
				{
					cur = parent;
					parent = parent->_pParent;
				}	//已经失衡需要进行调整
				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)
					{
                        //右左双旋
						RotateRL(parent);
					}
					else if (parent->_bf == -2 && cur->_bf == 1)
					{
                        //左右双旋
						RotateLR(parent);
					}
					break;
				}
				else
				{
					//理论而言不可能出现这个情况(此时小于-2或大于2)
					assert(false);
				}
			}
			return true;
		}

旋转调整 

有了前面插入时平衡因子变化逻辑的铺垫,失衡时的情况就简化成了:

失衡节点无非是-2或2,造就失衡节点的子节点无非是-1,1(产生高度变化)。排列组合后就出现了四种情况。于是出现四种旋转方式与其一一对应。

 右单旋

此时的失衡情况为:左左(插入在失衡节点的左节点的左子树中),此时失衡节点平衡因子为-2,产生高度变化的子节点平衡因子为-1。

所以右单旋适用于“左左”,即parent的平衡因子为-2,高度变化的子节点的平衡因子为-1。

 右单旋过程:
 

可以看到,调整后30和60这两个节点的平衡因子都为0了。

那么为什么要这么做? 

现在我们先来分析一下他们的大小关系。

比较大小后发现,60非常适合当作30的右子树根节点,同时又做b子树的父节点,因为60>b子树任意值>30。

所以右单旋就是将失衡节点及其右子树下压,当作高度出现变化的节点的右子树,再连接上b子树和60这个节点,就使30的左右子树高度一致了。左子树高度是h+1(1为插入的节点),右边也是h+1(1为原父节点),此时30成为新父节点。

// 右单旋
		void RotateR(Node* pParent)
		{
			Node* subL = pParent->_pLeft;
			Node* subLR = subL->_pRight;

			pParent->_pLeft = subLR;
			if (subLR)
				subLR->_pParent = pParent;
			subL->_pRight = pParent;
			Node* temp = pParent->_pParent;
			pParent->_pParent = subL;

			//pParent有可能为根,右单旋后应该更新根节点指向。
			if (pParent == _pRoot)
			{
				_pRoot = subL;
				_pRoot->_pParent = nullptr;
			}
			else
			{
				if (temp->_pLeft == pParent)
				{
					temp->_pLeft = subL;
				}
				else
				{
					temp->_pRight = subL;
				}
				subL->_pParent = temp;
			}
			pParent->_bf = subL->_bf = 0;
		}

左单旋

与右单旋同理,不过多赘述。

左单旋适用于“右右”,即parent的平衡因子为2,高度变化的子节点的平衡因子为1。

旋转过程: 

// 左单旋
		void RotateL(Node* pParent)
		{
			Node* subR = pParent->_pRight;
			Node* subRL = subR->_pLeft;

			pParent->_pRight = subRL;
			if (subRL)
				subRL->_pParent = pParent;
			subR->_pLeft = pParent;
			Node* temp = pParent->_pParent;
			pParent->_pParent = subR;

			//pParent有可能为根,右单旋后应该更新根节点指向。
			if (pParent == _pRoot)
			{
				_pRoot = subR;
				_pRoot->_pParent = nullptr;
			}
			else
			{
				if (temp->_pLeft == pParent)
				{
					temp->_pLeft = subR;
				}
				else
				{
					temp->_pRight = subR;
				}
				subR->_pParent = temp;
			}
			pParent->_bf = subR->_bf = 0;
		}

左右双旋

如图所示,当出现插入节点在失衡节点(90)其左孩子(30)的右孩子(60)下的子树时,就需要用到左右双旋,单旋是无法解决的。

即:失衡节点平衡因子为-2,且其左孩子的平衡因子为1时使用左右双旋,以下简称“左右”。

第一步,我们对30为根的子树进行左单旋。(第一次旋转后,失衡的状况从“左右”变成了可以使用右单旋的“左左”)

 第二步,我们对90为根的子树进行右单旋。

 代码实际上是对左单旋和右单旋的复用,然后再处理节点间的连接关系。

// 左右双旋
		void RotateLR(Node* pParent)
		{
			Node* subL = pParent->_pLeft;
			Node* subLR = subL->_pRight;
			int bf = subLR->_bf;

			RotateL(subL);
			RotateR(pParent);
			subLR->_bf = 0;

			if (bf == 1)
			{
				pParent->_bf = 0;
				subL->_bf = -1;
			}
			else if (bf == -1)
			{
				pParent->_bf = 1;
				subL->_bf = 0;
			}
			else
			{
				pParent->_bf = 0;
				subL->_bf = 0;
			}
		}

右左双旋

失衡节点平衡因子为2,且其左孩子的平衡因子为-1时使用左右双旋。

第一步,我们对90为根的子树进行右单旋。(第一次旋转后,失衡的状况从“右左”变成了可以使用左单旋的“右右”)

 第二步,我们对30为根的子树进行左单旋。

// 右左双旋
		void RotateRL(Node* pParent)
		{
			Node* subR = pParent->_pRight;
			Node* subRL = subR->_pLeft;
			int bf = subRL->_bf;

			RotateR(subR);
			RotateL(pParent);
			subRL->_bf = 0;

			if (bf == 1)
			{
				pParent->_bf = -1;
				subR->_bf = 0;
			}
			else if (bf == -1)
			{
				pParent->_bf = 0;
				subR->_bf = 1;
			}
			else
			{
				pParent->_bf = 0;
				subR->_bf = 0;
			}
		}

完整代码实现

#pragma once
#include<assert.h>
#include<vector>
#include<iostream>
using namespace std;

namespace AVL
{
	template<class T>
	struct AVLTreeNode
	{
		AVLTreeNode(const T& data = T())
			: _pLeft(nullptr)
			, _pRight(nullptr)
			, _pParent(nullptr)
			, _data(data)
			, _bf(0)
		{}

		AVLTreeNode<T>* _pLeft;
		AVLTreeNode<T>* _pRight;
		AVLTreeNode<T>* _pParent;
		T _data;
		int _bf;   // 节点的平衡因子
	};


	// AVL: 二叉搜索树 + 平衡因子的限制
	template<class T>
	class AVLTree
	{
		typedef AVLTreeNode<T> Node;
	public:
		AVLTree()
			: _pRoot(nullptr)
		{}

		Node* Find(const T& data)
		{
			Node* cur = _pRoot;
			while (cur)
			{
				if (cur->_data < data)
				{
					cur = cur->_pRight;
				}
				else if (cur->_data > data)
				{
					cur = cur->_pLeft;
				}
				else
				{
					return cur;
				}
			}

			return nullptr;
		}

		// 在AVL树中插入值为data的节点
		bool Insert(const T& data)
		{
			if (_pRoot == nullptr)
			{
				_pRoot = new Node(data);
				return true;
			}
			Node* parent = nullptr;
			Node* cur = _pRoot;

			while (cur)
			{
				if (data > cur->_data)
				{
					parent = cur;
					cur = cur->_pRight;
				}
				else if (data < cur->_data)
				{
					parent = cur;
					cur = cur->_pLeft;
				}
				else
				{
					return false;
				}
			}

			cur = new Node(data);
			if (parent->_data < data)
			{
				parent->_pRight = cur;
			}
			else
			{
				parent->_pLeft = cur;
			}
			cur->_pParent = parent;


			//调整平衡因子
			while (parent)
			{
				if (cur == parent->_pLeft)
				{
					parent->_bf--;
				}
				else
				{
					parent->_bf++;
				}

				//插入后恰好平衡了,跳出循环
				if (parent->_bf == 0)
				{
					break;
				}    //高度出现变化但还未失衡,向上走进行判断
				else if (parent->_bf == 1 || parent->_bf == -1)
				{
					cur = parent;
					parent = parent->_pParent;
				}	//已经失衡需要进行调整
				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)
					{
						RotateRL(parent);
					}
					else if (parent->_bf == -2 && cur->_bf == 1)
					{
						RotateLR(parent);
					}
					break;
				}
				else
				{
					// 理论而言不可能出现这个情况
					assert(false);
				}
			}
			return true;
		}

		// AVL树的验证
		bool IsAVLTree()
		{
			return _IsAVLTree(_pRoot);
		}

		int Size()
		{
			return _Size(_pRoot);
		}

		void InOrder()
		{
			_InOrder(_pRoot);
			cout << endl;
		}

		size_t Height()
		{
			return _Height(_pRoot);
		}

	private:
		void _InOrder(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}

			_InOrder(root->_pLeft);
			cout << root->_data << endl;
			_InOrder(root->_pRight);
		}

		bool _IsAVLTree(Node* root)
		{
			if (root == nullptr)
				return true;

			int leftHeight = _Height(root->_pLeft);
			int rightHeight = _Height(root->_pRight);
			// 不平衡
			if (abs(leftHeight - rightHeight) >= 2)
			{
				cout << root->_data << endl;
				return false;
			}

			// 顺便检查一下平衡因子是否正确
			if (rightHeight - leftHeight != root->_bf)
			{
				cout << root->_data << endl;
				return false;
			}

			return _IsAVLTree(root->_pLeft)
				&& _IsAVLTree(root->_pRight);
		}

		size_t _Height(Node* _pRoot)
		{
			if (_pRoot == nullptr) return 0;

			return max(_Height(_pRoot->_pLeft), _Height(_pRoot->_pRight)) + 1;
		}

		int _Size(Node* root)
		{
			return root == nullptr ? 0 : _Size(root->_pLeft) + _Size(root->_pRight) + 1;
		}

		// 右单旋
		void RotateR(Node* pParent)
		{
			Node* subL = pParent->_pLeft;
			Node* subLR = subL->_pRight;

			pParent->_pLeft = subLR;
			if (subLR)
				subLR->_pParent = pParent;
			subL->_pRight = pParent;
			Node* temp = pParent->_pParent;
			pParent->_pParent = subL;

			//pParent有可能为根,右单旋后应该更新根节点指向。
			if (pParent == _pRoot)
			{
				_pRoot = subL;
				_pRoot->_pParent = nullptr;
			}
			else
			{
				if (temp->_pLeft == pParent)
				{
					temp->_pLeft = subL;
				}
				else
				{
					temp->_pRight = subL;
				}
				subL->_pParent = temp;
			}
			pParent->_bf = subL->_bf = 0;
		}
		// 左单旋
		void RotateL(Node* pParent)
		{
			Node* subR = pParent->_pRight;
			Node* subRL = subR->_pLeft;

			pParent->_pRight = subRL;
			if (subRL)
				subRL->_pParent = pParent;
			subR->_pLeft = pParent;
			Node* temp = pParent->_pParent;
			pParent->_pParent = subR;

			//pParent有可能为根,右单旋后应该更新根节点指向。
			if (pParent == _pRoot)
			{
				_pRoot = subR;
				_pRoot->_pParent = nullptr;
			}
			else
			{
				if (temp->_pLeft == pParent)
				{
					temp->_pLeft = subR;
				}
				else
				{
					temp->_pRight = subR;
				}
				subR->_pParent = temp;
			}
			pParent->_bf = subR->_bf = 0;
		}
		// 右左双旋
		void RotateRL(Node* pParent)
		{
			Node* subR = pParent->_pRight;
			Node* subRL = subR->_pLeft;
			int bf = subRL->_bf;

			RotateR(subR);
			RotateL(pParent);
			subRL->_bf = 0;

			if (bf == 1)
			{
				pParent->_bf = -1;
				subR->_bf = 0;
			}
			else if (bf == -1)
			{
				pParent->_bf = 0;
				subR->_bf = 1;
			}
			else
			{
				pParent->_bf = 0;
				subR->_bf = 0;
			}
		}
		// 左右双旋
		void RotateLR(Node* pParent)
		{
			Node* subL = pParent->_pLeft;
			Node* subLR = subL->_pRight;
			int bf = subLR->_bf;

			RotateL(subL);
			RotateR(pParent);
			subLR->_bf = 0;

			if (bf == 1)
			{
				pParent->_bf = 0;
				subL->_bf = -1;
			}
			else if (bf == -1)
			{
				pParent->_bf = 1;
				subL->_bf = 0;
			}
			else
			{
				pParent->_bf = 0;
				subL->_bf = 0;
			}
		}

	private:
		Node* _pRoot;
	};
}

  • 17
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值