高阶数据结构:AVL树实现

AVL树是一种自平衡的二叉搜索树,通过插入操作后的旋转保持左右子树高度差不超过1,确保高效的查找性能。文章详细介绍了AVL树的插入过程,包括平衡因子的更新和四种旋转操作:左旋、右旋、左右旋和右左旋,以及如何验证AVL树的平衡性。

AVL树的概念

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

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)

例子:
请添加图片描述


AVL结构的定义

可以像下面这样定义成类,也可以定义成结构体,因为结构体的成员的默认权限是公开的(public),下面代码区的定义有点多余

template<class K,class V>
	class AVLtreeNode
	{
	public:
		//默认构造函数
		AVLtreeNode(const pair<K,V>& kv)
			:_left(nullptr)
			, _right(nullptr)
			, _parent(nullptr)
			,_kv(kv)
			, _bf(0)
		{}
		//三叉链
		AVLtreeNode<K,V>* _left;
		AVLtreeNode<K,V>* _right;
		AVLtreeNode<K,V>* _parent;
		pair<K,V> _kv;
		int _bf; //平衡因子 balance factor
	};

AVL树之插入

前言:因为搜索树满足一定的比较规则,下面的例子都以左子树都比其父亲节点小,右子树的节点比其父亲节点大。

AVL树的插入实现前面部分与普通的二叉搜索树没有不同,只是多了更新平衡因子和旋转这俩步。

  1. 寻找合适的插入位置,插入新节点
  2. 更新平衡因子(左子树高度减右子树高度)
  3. 平衡因子异常时,进行旋转

而其中平衡因子的更新至关重要,也可以分为俩种情况

  • 新插入的节点的父母节点的平衡因子被更新为-1,1时
    需要向上继续更新祖父母的平衡因子
  • 新插入的节点的父母节点的平衡因子被更新为0时
    不需要向上继续更新祖父母的平衡因子,也是更新平衡因子结束的信号

而这样做的原因也很简单,平衡因子被更新为0的节点,先前一定是有一边的子树高一点,而这次插入,正是插入到了较矮的那颗子树里,将高度差补平了,而对其的父母节点乃至祖父母节点的平衡因子毫无影响,因为没有高度的变化;而对于平衡因子被更新为-1,1的节点需要向上更新的原因也正是树的高度发生了变化

请添加图片描述

插入的完整实现代码

bool insert(const pair<K, V>& kv)
		{
			//第一次插入
			if (!_root)
			{
				_root = new Node(kv);
				return true;
			}

			//先寻找插入的位置
			Node* cur = _root;
			Node* parent = nullptr;
			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;
			}
			else
			{
				parent->_right =cur;
				cur->_parent = parent;
			}

			//更新平衡因子和调节平衡 平衡因子等于右子树高度减左子树高度
			while (parent)
			{
				if (cur == parent->_left)
				{
					--parent->_bf;
				}
				else if (cur == parent->_right)
				{
					++parent->_bf;
				}

				//更新平衡因子
				if (parent->_bf == 0)
				{
					//平衡因子为0,更新结束,因为只是将较矮的新增了个节点,堆父亲没有影响
					break;
				}
				else if (parent->_bf == 1 || parent->_bf == -1) //继续更新
				{
					cur = parent;
					parent = parent->_parent;
				}
				else if (parent->_bf == 2 || parent->_bf == -2) //平衡出现错误了 需要进行调整了
				{
					//判断旋转方式 
					//1.较高子树为左子树 且插入在较高子树的左子树
					if (parent->_bf == -2 && cur->_bf == -1)
					{
						rotateRight(parent);
					}
					//2.较高子树为右子树 且插入在较高子树的右子树
					else if (parent->_bf == 2 && cur->_bf == 1)
					{
						rotateLeft(parent);
					}
					//3..较高子树为左子树 且插入在较高子树的右子树
					else if(parent->_bf==-2&& cur->_bf==1)
					{
						//进行左右单旋
						rotateLR(parent);
					}
					//4.较高子树为右子树 且插入在较高子树的左子树
					else if (parent->_bf == 2 && cur->_bf == -1)
					{
						//进行右左单旋
						rotateRL(parent);
					}
					else
					{
						assert(false);
					}
					break;
				}
				else
				{
					//走到这里就是代码逻辑出问题了
					assert(false);
				}

				
			}
			return true;
		}

AVL树之旋转

AVL树的旋转分别针对以下几种情况

  • 较高子树为右子树,新插入的节点插入到该较高右子树的右子树里:进行左单旋
    抽象图:
    请添加图片描述
    为了方便操作,我们将平衡因子异常的节点设为parent,异常节点的右节点设为subR,subR的左节点设为subRL

步骤:

  1. subRL更新为parent的右节点,如果存在的话,且将subRL的父亲节点更新为parent
  2. 用Parentparent保存parent的父母亲节点
  3. 将subR的左子节点更新为parent,parent的父母亲节点更新为subR
  4. 将subR的父母亲节点更新为Parentparent,并分情况讨论:
    1.若parent为根节点,即将subL更新为根节点
    2.若parent为其他节点的子树,通过Parentparent判断parent所在那颗子树,然后将Parentparent的子树位置更新为subR
  5. 最后将parent和subR的平衡因子更新为0

请添加图片描述
代码实现

//左单旋
		void rotateLeft(Node* Parent)
		{
			Node* subR = Parent->_right;
			Node* subRL = subR->_left;

			//将subLR交给父亲节点的右节点
			Parent->_right = subRL;
			if (subRL)
				subRL->_parent = Parent;

			//开始左旋
			subR->_left = Parent;
			Node* Parentparent = Parent->_parent;
			//更新subR的父亲节点
			subR->_parent = Parentparent;

			Parent->_parent = subR;

			//连接上面的节点
			if (_root == Parent)
			{
				_root = subR;
				_root->_parent = nullptr;
			}
			else
			{
				if (Parentparent->_left == Parent)
				{
					Parentparent->_left = subR;
				}
				else
				{
					Parentparent->_right = subR;
				}
			}

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

  • 较高子树为左子树,新插入的节点插入到该较高左子树的左子树里 :进行右单旋
    抽象图如下:
    请添加图片描述

为了方便操作,我们将平衡因子异常的节点设为parent,异常节点的左节点设为subL,subL的右节点设为subLR

分别举俩个例子说明步骤:

  1. subLR更新为parent的左节点,如果存在的话,且将subLR的父亲节点更新为parent
  2. 用Parentparent保存parent的父母亲节点
  3. 将subL的右子节点更新为parent,parent的父母亲节点更新为subL
  4. 将subL的父母亲节点更新为Parentparent,并分情况讨论:
    1.若parent为根节点,即将subL更新为根节点
    2.若parent为其他节点的子树,通过Parentparent判断parent所在那颗子树,然后将Parentparent的子树位置更新为subL
  5. 最后将parent和subL的平衡因子更新为0
    请添加图片描述
    h=2,h=3……的情况也是类似的,就不在此处举例了

代码实现

void rotateRight(Node * Parent)
		{
			Node* SubL = Parent->_left;
			Node* SubLR = SubL->_right;

			//将SubLR交给Parent
			Parent->_left = SubLR;
			if (SubLR)
			{
				//SubLR不为空才更新的父亲节点
				SubLR->_parent = Parent;
			}

			//将Parent进行旋转
			SubL->_right = Parent;
			Node* Parentparent = Parent->_parent;
			SubL->_parent = Parentparent;
			//更新parent的_parent
			Parent->_parent = SubL;

			//检查Parent是否为根节点 若为根节点 则更新根节点 连接子树
			if (_root == Parent)
			{
				_root = SubL;
				_root->_parent = nullptr;
			}
			else
			{
				if (Parentparent->_left == Parent)
				{
					Parentparent->_left = SubL;
				}
				else if(Parentparent->_right == Parent)
				{
					Parentparent->_right = SubL;
				}
			}
			//更新Parent和SubL的平衡因子
			Parent->_bf = SubL->_bf = 0;
		}

  • 较高子树为左子树,新插入的节点插入到该较高左子树的右子树里 :进行左右双旋
    抽象图为:
    请添加图片描述
    操作步骤:
  1. 左旋异常节点的左子树
  2. 再右旋异常节点本身
  3. 更新平衡因子

而对于双旋来说,只需要复用前面单旋的代码实现即可,但其平衡因子的更新却发生了变化,需要分类讨论
请添加图片描述
由上图可分为以下三种情况

  • 情况一:subLR平衡因子为-1时
    subL:0
    parent:1
    subLR:0
  • 情况二:subLR平衡因子为1时
    subL:-1
    parent:0
    subLR:
  • 情况三:subLR平衡因子为0时
    subL:0
    parent:0
    subLR:0

代码实现

void rotateLR(Node* Parent)
		{
			//记录以下节点
			Node *subL = Parent->_left;
			Node* subLR = subL->_right;
			int bf = subLR->_bf;
			//先左旋其左子树 再右旋其本身
			rotateLeft(Parent->_left);
			rotateRight(Parent);

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

  • 较高子树为右子树,新插入的节点插入到该较高右子树的左子树里 进行右左双旋
    抽象图解为:
    请添加图片描述
    与上面那种情况类似
    操作步骤:
  1. 右旋异常节点的右子树
  2. 再左
    旋异常节点本身
  3. 更新平衡因子

而对于双旋来说,只需要复用前面单旋的代码实现即可,但其平衡因子的更新却发生了变化,需要分类讨论
请添加图片描述

由上图可分为以下三种情况

  • 情况一:subRL平衡因子为-1时
    subR:1
    parent:0
    subRL:0
  • 情况二:subRL平衡因子为1时
    subR:0
    parent:-1
    subRL:0
  • 情况三:subRL平衡因子为0时
    subR:0
    parent:0
    subRL:0

代码实现

void rotateRL(Node* Parent)
		{
			//记录以下节点
			Node* subR = Parent->_right;
			Node* subRL = subR->_left;
			int bf = subRL->_bf;
			//先右旋其右子树 再左旋其本身
			rotateRight(subR);
			rotateLeft(Parent);

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

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

AVL树之查找

同插入原理一般,比较数值,与当前节点进行比较,比此节点大,继续到右子树寻找,比此节点小,继续到左子树寻找

bool find(const K& key)
{
	Node* cur = _root;
	while (cur)
	{
		if (cur->_kv.first >key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (cur->_kv.first < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else //找到相等的
		{
			return true;
		}
	}
	return false;
}

AVL树之验证

AVL树的验证主要分为俩部分
1.是否为平衡二叉搜索树
2.平衡因子是否有错

代码实现

		bool IsBalance()
		{
			return _IsBalance(_root);
		}

		int Height(Node* root)
		{
			if (root == NULL)
				return 0;
			int leftHeight = Height(root->_left);
			int rightHeight = Height(root->_right);
			return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
		}

		bool _IsBalance(Node* root)
		{
			if (root == NULL)
				return true;

			// 对当前树进行检查
			int leftHeight = Height(root->_left);
			int rightHeight = Height(root->_right);

			if (rightHeight - leftHeight != root->_bf)
			{
				cout << root->_kv.first << "现在是:" << root->_bf << endl;
				cout << root->_kv.first << "应该是:" << rightHeight - leftHeight << endl;
				return false;
			}

			return abs(rightHeight - leftHeight) < 2
				&& _IsBalance(root->_left)
				&& _IsBalance(root->_right);
		}

AVL性能分析

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


总代码:

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

//namespace AVL
//{
	template<class K,class V>
	class AVLtreeNode
	{
	public:
		//默认构造函数
		AVLtreeNode(const pair<K,V>& kv)
			:_left(nullptr)
			, _right(nullptr)
			, _parent(nullptr)
			,_kv(kv)
			, _bf(0)
		{}

		AVLtreeNode<K,V>* _left;
		AVLtreeNode<K,V>* _right;
		AVLtreeNode<K,V>* _parent;
		pair<K,V> _kv;
		int _bf; //平衡因子 balance factor
	};

	template<class K,class V>
	class AVLTree
	{
		typedef AVLtreeNode<K, V> Node;
	public:
		AVLTree()
			:_root(nullptr)
		{}
		void Inorder()
		{
			_Inorder(_root);
		}
		void _Inorder(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}
			//中序遍历
			_Inorder(root->_left);
			cout << root->_kv.first << ' ' <<root->_kv.second<< endl;
			_Inorder(root->_right);
		}
		bool insert(const pair<K, V>& kv)
		{
			//第一次插入
			if (!_root)
			{
				_root = new Node(kv);
				return true;
			}

			//先寻找插入的位置
			Node* cur = _root;
			Node* parent = nullptr;
			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;
			}
			else
			{
				parent->_right =cur;
				cur->_parent = parent;
			}

			//更新平衡因子和调节平衡 平衡因子等于右子树高度减左子树高度
			while (parent)
			{
				if (cur == parent->_left)
				{
					--parent->_bf;
				}
				else if (cur == parent->_right)
				{
					++parent->_bf;
				}

				//更新平衡因子
				if (parent->_bf == 0)
				{
					//平衡因子为0,更新结束,因为只是将较矮的新增了个节点,堆父亲没有影响
					break;
				}
				else if (parent->_bf == 1 || parent->_bf == -1) //继续更新
				{
					cur = parent;
					parent = parent->_parent;
				}
				else if (parent->_bf == 2 || parent->_bf == -2) //平衡出现错误了 需要进行调整了
				{
					//判断旋转方式 
					//1.较高子树为左子树 且插入在较高子树的左子树
					if (parent->_bf == -2 && cur->_bf == -1)
					{
						rotateRight(parent);
					}
					//2.较高子树为右子树 且插入在较高子树的右子树
					else if (parent->_bf == 2 && cur->_bf == 1)
					{
						rotateLeft(parent);
					}
					//3..较高子树为左子树 且插入在较高子树的右子树
					else if(parent->_bf==-2&& cur->_bf==1)
					{
						//进行左右单旋
						rotateLR(parent);
					}
					//4.较高子树为右子树 且插入在较高子树的左子树
					else if (parent->_bf == 2 && cur->_bf == -1)
					{
						//进行右左单旋
						rotateRL(parent);
					}
					else
					{
						assert(false);
					}
					break;
				}
				else
				{
					//走到这里就是代码逻辑出问题了
					assert(false);
				}

				
			}
			return true;
		}

		void rotateRight(Node * Parent)
		{
			Node* SubL = Parent->_left;
			Node* SubLR = SubL->_right;

			//将SubLR交给Parent
			Parent->_left = SubLR;
			if (SubLR)
			{
				//SubLR不为空才更新的父亲节点
				SubLR->_parent = Parent;
			}

			//将Parent进行旋转
			SubL->_right = Parent;
			Node* Parentparent = Parent->_parent;
			SubL->_parent = Parentparent;
			//更新parent的_parent
			Parent->_parent = SubL;

			//检查Parent是否为根节点 若为根节点 则更新根节点 连接子树
			if (_root == Parent)
			{
				_root = SubL;
				_root->_parent = nullptr;
			}
			else
			{
				if (Parentparent->_left == Parent)
				{
					Parentparent->_left = SubL;
				}
				else if(Parentparent->_right == Parent)
				{
					Parentparent->_right = SubL;
				}
			}
			//更新Parent和SubL的平衡因子
			Parent->_bf = SubL->_bf = 0;
		}
		//左单旋
		void rotateLeft(Node* Parent)
		{
			Node* subR = Parent->_right;
			Node* subRL = subR->_left;

			//将subLR交给父亲节点的右节点
			Parent->_right = subRL;
			if (subRL)
				subRL->_parent = Parent;

			//开始左旋
			subR->_left = Parent;
			Node* Parentparent = Parent->_parent;
			//更新subR的父亲节点
			subR->_parent = Parentparent;

			Parent->_parent = subR;

			//连接上面的节点
			if (_root == Parent)
			{
				_root = subR;
				_root->_parent = nullptr;
			}
			else
			{
				if (Parentparent->_left == Parent)
				{
					Parentparent->_left = subR;
				}
				else
				{
					Parentparent->_right = subR;
				}
			}

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

		void rotateLR(Node* Parent)
		{
			//记录以下节点
			Node *subL = Parent->_left;
			Node* subLR = subL->_right;
			int bf = subLR->_bf;
			//先左旋其左子树 再右旋其本身
			rotateLeft(Parent->_left);
			rotateRight(Parent);

			if (bf == 0)
			{
				subLR->_bf = 0;
				Parent->_bf = 0;
				subL->_bf = 0;
			}
			else if (bf == 1)
			{
				subLR->_bf = 0;
				Parent->_bf = 0;
				subL->_bf = -1;
				
			}
			else if (bf == -1)
			{
				subLR->_bf = 0;
				Parent->_bf = 1;
				subL->_bf = 0;
			}
			else
			{
				assert(false);
			}
		}
		void rotateRL(Node* Parent)
		{
			//记录以下节点
			Node* subR = Parent->_right;
			Node* subRL = subR->_left;
			int bf = subRL->_bf;
			//先右旋其右子树 再左旋其本身
			rotateRight(subR);
			rotateLeft(Parent);

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

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

		bool IsBalance()
		{
			return _IsBalance(_root);
		}

		int Height(Node* root)
		{
			if (root == NULL)
				return 0;
			int leftHeight = Height(root->_left);
			int rightHeight = Height(root->_right);
			return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
		}

		bool _IsBalance(Node* root)
		{
			if (root == NULL)
				return true;

			// 对当前树进行检查
			int leftHeight = Height(root->_left);
			int rightHeight = Height(root->_right);

			if (rightHeight - leftHeight != root->_bf)
			{
				cout << root->_kv.first << "现在是:" << root->_bf << endl;
				cout << root->_kv.first << "应该是:" << rightHeight - leftHeight << endl;
				return false;
			}

			return abs(rightHeight - leftHeight) < 2
				&& _IsBalance(root->_left)
				&& _IsBalance(root->_right);
		}
	private:

		Node* _root;
	};
//}

😗 😗 文章到此就结束了,如果有什么错误的地方,希望各位观看的大佬能指出来,相互学习,相互进步,以及AVL树的删除因为面试不常考,可能以后会更新。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值