【数据结构】AVL树的实现

文章目录


前言

在了解完二叉搜索树之后,我们在来看看二叉搜索树的一个进阶版本----AVL树


一、AVL树的概念以及性质

概念: AVL树是一种自平衡的二叉搜索树 ,通过旋转操作保持平衡(本质是通过控制高度差去控制平衡的 ),使得插入,查找和删除的时间复杂度达到O (log n),
性质: 一棵AVL树只有两种状态:

  • 空树
  • 满足下列性质的二叉搜索树:
    1.左右子树都是AVL树
    2.左右子树的高度差的绝对值不差过1

这里为什么高度差是1而不是0呢,因为在某些情况下是无法做到0的,比如2个节点的时候,或者4个节点的时候等等

二、AVL的实现

这里为了我们方便实现AVL树,我们引进了平衡因子的概念。

  • 平衡因子=右子树的高度-左子树的高度

注意:每个节点都有平衡因子,而且平衡因子的值大小分别是-1/0/1这三个值,如果是这三个值则满足平衡AVL树的性质,说明它是一棵平衡二叉搜索树。如果是-2/2,则不满足AVL树的性质,不是AVL树,这个时候就需要通过旋转来把它调整成一棵AVL树。所以平衡因子的作用就是为了方便我们观察和控制树是否平衡

1.AVL树的结构

AVL树的结构跟二叉搜索树的结构是差不多的,只是AVL树的每个节点是三叉链的结构而且还引入了两个变量,一是平衡因子,二是指向父亲节点指针。

cpp 复制代码
#include<iostream>
#include<assert.h> 
using namespace std;
template<class T,class V>
struct AVLTreeNode//每个节点的信息
{
	pair<T, V> _kv;//这里pair可以理解成key/vlaue这两个值合并成的一个对象
	AVLTreeNode<T, V>* left;
	AVLTreeNode<T, V>* right;

	AVLTreeNode<T, V>* parent;//指向父亲节点的指针

	int _bf;//平衡因子

	AVLTreeNode(const pair<T, V>& kv)
		:_kv(kv) 
		, left(nullptr)  
		, right(nullptr) 
		, parent(nullptr) 
		, _bf(0) 
	{} 
};
template<class T,class V>
class AVLTree
{
public:
	typedef AVLTreeNode<T, V> Node;
private:
	Node* _root; 
};

2.AVL的插入

AVL树的插入分为以下三步:

  • 按照二叉搜索树的规则插入
  • 新增节点之后,更新平衡因子,如果新增节点在parent的右子树,那parent的平衡因子就++,如果在左子树,那就--。
  • 判断平衡因子在跟新的过程中是否出现不平衡,对平衡的直接插入新节点,对不平衡的就对子树进行旋转使其便平衡

3.平衡因子更新

判断平衡因子是否需要向上更新:

  • 判断parent节点的子树的高度是否变化,若有变化,那就需要向上更新平衡因子。若没有,那就不需要向上更新。

判断平衡因子是否停止更新:

  • 更新之后parent的节点平衡因子是为0,停止更新平衡因子。因为在没有更新之前的平衡因子是1或者-1,说明parent的子树的高度是一边高一边低的。这个时候插入的新节点在子树低的一边。但是子树的高度并没有发生变化,所以并不会影响到parent节点的父亲节点的平衡因子。

  • 若更新之后parent节点的平衡因子的等于-1或1。这个时候要向parent节点的祖先节点的平衡因子进行更新。因为没有更新之前parent的平衡因子一定是0,说明parent节点的左右子树的高度是一样的。但是当新增节点之后,parent所在节点的子树高度发生改变。这是就要向上更新平衡因子。

  • 更新之后的parent的平衡因子是-2或2,这时说明树已经不平衡了。这时就需要旋转让他变平衡。

4.AVL树的插入以及平衡因子更新的代码

cpp 复制代码
bool Insert(const pair<T, V>& kv)
{
	Node* parent = nullptr;
	Node* cur = _root;
	if (_root == nullptr)
	{
		_root = new Node(kv);
		return true;
	}
	while (cur)
	{
		if (cur->_kv.first < kv.first)
		{
			parent = cur;
			cur = cur->right;
		}
		else if (cur->_kv.first > kv.first)
		{
			parent = cur;
			cur = cur->left;
		}
		else return false;
	}
	cur = new Node(kv);
	if (parent->_kv.first < kv.first)
	{
		parent->right = cur;
	}
	else if (parent->_kv.first > kv.first)
	{
		parent->left = cur;
	}

	cur->parent = parent;
	//更新平衡因子
	while (parent)
	{
		//判断新增节点在parent的左边还是右边,右边++,左边--
		if (parent->left == cur)
		{
			parent->_bf--;
	    }
		else
		{
			parent->_bf++;
		}

		//平衡因子停止更新的条件
		if (parent->_bf == 0)break;
		else if (parent->_bf == 1 || parent->_bf == -1)
		{
			//向上更新
			cur = parent;
			parent = parent->parent;
		}
		else if (parent->_bf == 2 || parent->_bf == -2)
		{
			//旋转
			//右单旋
			if (parent->_bf == -2 && cur->_bf == -1)
			{
				RotateR(parent);
				break;
			}
			//左单旋
			else if (parent->_bf == 2 && cur->_bf == 1)
			{
				RotateL(parent);
				break;

			}
			//左右双旋
			else if (parent->_bf == -2 && cur->_bf == 1)
			{
				RotateLR(parent);
				break;

			}
			//右左双旋
			else if (parent->_bf == 2 && cur->_bf == -1)
			{
				RotateRL(parent);
				break;

			}
			else assert(false);
		}
		else
		{
			assert(false);
		}
	}
	return true;
}

三、旋转

旋转分为四种情况:

  • 右单旋
  • 左单旋
  • 左右双旋
  • 右左双旋

那这几种旋转分别在什么情况下使用呢?假设我们规定当插入的节点为cur,其父亲节点为parent,如果要向上跟新就保持这样的逻辑:

cpp 复制代码
cur=parent;
parent=cur->parent;

保持上面的逻辑直到需要旋转为止,旋转的前提有以下几种:

  • 当parent的平衡因子为-2,cur的平衡因子为-1,这个时候就要右旋转。
  • 当parent的平衡因子为2,cur的平衡因子为1,这个时候就要左旋转。
  • 当parent的平衡因子为2,cur的平衡因子为-1,这个时候就要右左旋转。
  • 当parent的平衡因子为-2,cur的平衡因子为1,这个时候就要左右旋转。

注意: 旋转的原则是要保持二叉搜索树的性质不变

1.右单旋

右单旋的所有情况可以用下图概括。注意以下只是抽象图,具体还得看h的高度(h>=0),高度越高其情况就越复杂。

右单旋的步骤:

  • 先让subLR作为parent的左子树
  • 再让parent作为subL的右子树
  • 然后再把subL作为整棵树的根节点
  • 最后在更新平衡因子

**注意:**经过观察,在右单旋之后,树的高度变成了跟插入之前的一样。所以在旋转之后就不用向上更新平衡因子了。

右单旋代码

cpp 复制代码
void RotateR(Node* parent)
{
	Node* subL = parent->left;
	Node* subLR = parent->right;   
	  
	//旋转
	parent->left = subLR;
	if (subLR)subLR->parent = parent;  
	//提前记录parent的父亲节点
	Node* parentparent = parent->parent; 

	subL->right = parent;
	parent->parent = subL;  
	
	if (parentparent == nullptr)//parent之前是没有父亲节点,它就是根节点
	{
		_root = subL;//更新根节点
		subL->parent = nullptr;
	}
	else
	{
		subL->parent = parentparent; 
		if (parentparent->left == parent)//之前是在左边
		{
			parentparent->left = subL; //现在更新指向新的节点  
		}
		else if (parentparent->right == parent)
		{
			parentparent->right = subL; 
		}
	}
	subL->_bf = parent->_bf = 0;//更新平衡因子
}

注意:由于我们实现的节点是三叉链的结构,改变节点的同时也要改变parent指针的指向。

3.左单旋

左单旋的逻辑跟右单选的逻辑是一样,只是需要改变以下方向就行了。如图所示:

左单旋的步骤:

  • 先让subRL作为parent的右子树
  • 再让parent作为subR的左子树
  • 然后再把subR作为整棵树的根节点
  • 最后在更新平衡因子

在旋转之后各子树的高度是一致的,所以不需要向上跟新平衡因子。但是要保持二叉搜索树的规则不变。

左单旋的代码

cpp 复制代码
void RotateL(Node* parent) //左单旋
{
	Node* subR = parent->right;
	Node* subRL = subR->left;  
	Node* pparent = parent->parent; 
	 
	//旋转
	parent->right = subRL;
	if (subRL) subRL->parent = parent; 
	parent->parent = subR;  
	subR->left = parent;
	if (pparent == nullptr)
	{
		_root = subR;
		subR->parent = nullptr;
	}
	else
	{
		if (pparent->left == parent)
		{
			pparent->left = subR;
		}
		else if (pparent->right == parent)
		{
			pparent->right = subR;
		}
		else assert(false);
	}
	parent->_bf = subR->_bf = 0; 
}

3.右左双旋

造成需要右左双旋的情况是,树的高度并不是单纯的一遍高。不过右左双选的重难点不在于旋转,而是在于平衡因子的更新。 我们先看图:

右左双旋的步骤

  • 先进行右单旋
  • 在进行左单旋
  • 最后跟新平衡因子

右左双旋的代码

通过上面张图可以清晰看出在这三种情况下最终的旋转结果的平衡因子是不一样。但是通过观察发现,因为只有parent,subR,subRL这上个节点的平衡因子在跟新,然而subRL的平衡因子会影响到其他两个节点的平衡因子。所以这里我们会先记录一下subRL的平衡因子来决定其他两个节点的平衡因子。

cpp 复制代码
void RotateRL(Node* parent)//右左双旋
{
	Node* subR = parent->right;
	Node* subRL = subR->left;
	int bf= subRL->_bf;    
	RotateR(parent->right);
	RotateL(parent);
	if (bf == 0)
	{
		subR->_bf = 0;
		subRL->_bf = 0;
		parent->_bf = 0;
	}
	else if (bf == -1)
	{
		subR ->_bf = 1;
		subRL->_bf = 0;
		parent->_bf = 0;
	}
	else if (bf == 1)
	{
		parent->_bf = -1;
		subR->_bf = 0;
		subRL->_bf = 0;
	}
	else assert(false);
}

4.左右双旋

左右双旋的逻辑和右左双旋的逻辑是一样的,只是方向相反而已。所以小编这里不作太多的解释了。直接给大家上代码。

左右双旋的步骤:

  • 先左单旋
  • 在右单旋
  • 最后跟新平衡因子

左右双旋的代码

cpp 复制代码
void RotetLR(Node* parent) //左右双旋
{
	Node* subL = parent->left;
	Node* subLR = subL->right;
	int bf = subLR->_bf;
	RotateL(parent->left);
	RotateR(parent); 
	if (bf == 0)
	{
		subL->_bf = 0;
		subLR->_bf = 0;
		parent->_bf = 0;
	}
	else if (bf == 1)
	{
		subLR->_bf = 0;
		subL->_bf = -1;
		parent->_bf = 0;
	}
	else if (bf == -1)
	{
		subL->_bf = 0;
		subLR->_bf = 0;
		parent->_bf = 0;
	}
	else assert(false);
}

四、AVL树平衡检测

我们怎样判断我们写出来的这棵树符合AVL树的规则呢?这时我们可能会想到用每个节点的平衡因子进行判断,超过1就是不是AVL树,当然这种方法也可以。不过通过检查左右子树的高度差进行验证,不光验证了是否是AVL树,而且也检查了平衡因子在跟新的过程是否出现了问题。

cpp 复制代码
int _Height(Node* root)
{
	if (root == nullptr)
		return 0;
	int leftHeight = _Height(root->left);
	int rightHeight = _Height(root->right);
	return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
bool _IsBalanceTree(Node* root)
{
	// 空树也是AVL树
	if (nullptr == root)
		return true;
	// 计算Root结点的平衡因⼦:即Root左右⼦树的⾼度差
	int leftHeight = _Height(root->left);
	int rightHeight = _Height(root->right);
	int diff = rightHeight - leftHeight;
	// 如果计算出的平衡因⼦与Root的平衡因⼦不相等,或者
	// Root平衡因⼦的绝对值超过1,则⼀定不是AVL树
	if (abs(diff) >= 2)
	{
		cout << root->_kv.first << "⾼度差异常" << endl;
		return false;
	}
	if (root->_bf != diff)
	{
		cout << root->_kv.first << "平衡因⼦异常" << endl;
		return false;
	}
	// Root的左和右如果都是AVL树,则该树⼀定是AVL树
	return _IsBalanceTree(root->left) && _IsBalanceTree(root->right);
}

五、AVL树的整体代码展现

1.AVL.h文件

cpp 复制代码
#include<iostream>
#include<assert.h> 
#include<vector>
using namespace std;
template<class T, class V>
struct AVLTreeNode
{
	pair<T, V> _kv;//这里pair可以理解成key/vlaue这两个值合并成的一个对象
	AVLTreeNode<T, V>* left;
	AVLTreeNode<T, V>* right;

	AVLTreeNode<T, V>* parent;//指向父亲节点的指针

	int _bf;//平衡因子

	AVLTreeNode(const pair<T, V>& kv)
		:_kv(kv)
		, left(nullptr)
		, right(nullptr)
		, parent(nullptr)
		, _bf(0)
	{}
};
template<class T, class V>
class AVLTree
{
public:
	typedef AVLTreeNode<T, V> Node;
	bool Insert(const pair<T, V>& kv)
	{
		Node* parent = nullptr;
		Node* cur = _root;
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}
		while (cur)
		{
			if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->right;
			}
			else if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->left;
			}
			else return false;
		}
		cur = new Node(kv);
		if (parent->_kv.first < kv.first)
		{
			parent->right = cur;
		}
		else if (parent->_kv.first > kv.first)
		{
			parent->left = cur;
		}

		cur->parent = parent;
		//更新平衡因子
		while (parent)
		{
			//判断新增节点在parent的左边还是右边,右边++,左边--
			if (parent->left == cur)
			{
				parent->_bf--;
		    }
			else
			{
				parent->_bf++;
			}

			//平衡因子停止更新的条件
			if (parent->_bf == 0)break;
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				//向上更新
				cur = parent;
				parent = parent->parent;
			}
			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)
				{
					RotateLR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(parent);
				}
				else assert(false);
				break;
			}
			else
			{
				assert(false);
			}
		}
		return true;
	}
	Node* find(const pair<T, V>& kv)//查找
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < kv.first)
			{
				cur = cur->right;
			}
			else if (cur->_kv.first > kv.first)
			{
				cur = cur->left;
			}
			else return cur;
		}
		return nullptr;
	}
	
	void RotateRL(Node* parent)//右左双旋 
	{
		Node* subR = parent->right;
		Node* subRL = subR->left;
		int bf = subRL->_bf;

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

		if (bf == 0)
		{
			subR->_bf = subRL->_bf = parent->_bf = 0;
		}
		else if (bf == 1)
		{
			subRL->_bf = 0;
			subR->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			subRL->_bf = 0;
			subR->_bf = 1;
			parent->_bf = 0;
		}
		else assert(false);
	}
	void RotateLR(Node* parent)//左右双旋
	{
		Node* subL = parent->left;
		Node* subLR = subL->right;
		int bf = subLR->_bf;
		RotateL(parent->left);
		RotateR(parent);
		if (bf == 0)
		{
			subL->_bf = subLR->_bf = parent->_bf = 0;
		}
		else if (bf == 1)
		{
			subLR->_bf = 0;
			subL->_bf = -1;
			parent->_bf = 0;
		}
		else if (bf == -1)
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->_bf = 1;
		}
		else assert(false);
	}
	void RotateL(Node* parent)//左旋转
	{
		Node* subR = parent->right;
		Node* subRL = subR->left;
		Node* parentparent = parent->parent;
		//旋转
		parent->right = subRL;
		if (subRL)subRL->parent = parent;
		subR->left = parent;
		parent->parent = subR;

		if (parentparent == nullptr)
		{
			_root = subR;
			subR->parent = nullptr;
		}
		else
		{
			subR->parent = parentparent;  
			if (parentparent->left == parent) 
			{
				parentparent->left = subR;  
			}
			else if (parentparent->right == parent) 
			{
				parentparent->right = 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;
		//提前记录parent的父亲节点
		Node* parentparent = parent->parent;

		subL->right = parent;
		parent->parent = subL;

		if (parentparent ==nullptr)
		{
			_root = subL;
			subL->parent = nullptr;
		}
		else
		{
			subL->parent = parentparent;
			if (parentparent->left == parent)
			{
				parentparent->left = subL;
			}
			else if (parentparent->right == parent)
			{
				parentparent->right = subL;
			}
		}
		subL->_bf = parent->_bf = 0;//更新平衡因子
	}

	void InOrder()
	{
		_InOrder(_root);
	}
	bool IsBalanceTree()
	{
		return _IsBalanceTree(_root);

	}
    int  Height()
	{
		return _Height(_root);         
	}
	int Size()
	{
		return _Size(_root); 
	}
private:
	int _Size(Node* root)
	{
		if (root == nullptr)
			return 0;
		return _Size(root->left) + _Size(root->right)+1;  
	}
   int _Height(Node* root)  
	{
		if (root == nullptr)
			return 0;
		int  longleft = _Height(root->left); 
		int  longright = _Height(root->right); 
		return longleft > longright ? longleft + 1 : longright + 1;
	}
	
	bool _IsBalanceTree(Node* root)
	{
		// 空树也是AVL树
		if (nullptr == root)
			return true;
		// 计算Root结点的平衡因⼦:即Root左右⼦树的⾼度差
		int leftHeight = _Height(root->left);
		int rightHeight = _Height(root->right);
		int diff = rightHeight - leftHeight;
		// 如果计算出的平衡因⼦与Root的平衡因⼦不相等,或者
		// Root平衡因⼦的绝对值超过1,则⼀定不是AVL树
		if (abs(diff) >= 2)
		{
			cout << root->_kv.first << "⾼度差异常" << endl;
			return false;
		}
		if (root->_bf != diff)
		{
			cout << root->_kv.first << "平衡因⼦异常" << endl;
			return false;
		}
		//Root的左和右如果都是AVL树,则该树⼀定是AVL树
		return _IsBalanceTree(root->left) && _IsBalanceTree(root->right);
	}

	void _InOrder(Node* _root)
	{
		if (_root == nullptr)
		{
			return;
		}
		_InOrder(_root->left);
		cout << _root->_kv.first << ":" << _root->_kv.second << " ";
		_InOrder(_root->right);

	}

private:
	Node* _root = nullptr;
};

1.AVL.cpp文件

cpp 复制代码
#include"AVL.h"
void TestAVLTree1()
{
	AVLTree<int, int> t; 
	// 常规的测试⽤例
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
// 特殊的带有双旋场景的测试⽤例
	//int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	//int a[] = {1,2,3,4,5,6,7,8 };


	int a[] = { 4,7,8 };

	for (auto e : a)
	{
		t.Insert({ e, e });
	}
	t.InOrder();
	cout << t.IsBalanceTree () << endl;     
}
void TestAVLTree2()
{
	const int N = 100000;
	vector<int> v;
	v.reserve(N);
	srand(time(0));
	for (size_t i = 0; i < N; i++)
	{
		v.push_back(rand() + i);
	}
	size_t begin2 = clock();
	AVLTree<int, int> t;
	for (auto e : v)
	{
		t.Insert(make_pair(e, e));
	}
	size_t end2 = clock();
	cout << "Insert:" << end2 - begin2 << endl;
	cout << t.IsBalanceTree() << endl;
	cout << "Height:" << t.Height() << endl;   
	cout << "Size:" << t.Size() << endl;   
	//size_t begin1 = clock();
	 确定在的值
	///*for (auto e : v)
	//{
	//t.Find(e);
	//}*/
	 随机值
	//for (size_t i = 0; i < N; i++)
	//{
	//	t.find((rand() + i)); 
	//}
	//size_t end1 = clock();
	//cout << "Find:" << end1 - begin1 << endl;
}
int main()
{
	TestAVLTree2();
}

总结

今天的分享的就这里了哦。我们下期再见,制作不易点个赞在走吧!

相关推荐
AL流云。3 小时前
【优选算法】分治
数据结构·算法·leetcode·排序算法
行驶4 小时前
数据结构 - 栈与队列
数据结构
haoly19894 小时前
数据结构篇--分离链表vs线性探测
数据结构
andyweike4 小时前
数据结构-文件
数据结构
andyweike4 小时前
数据结构-线性表
数据结构
思捻如枫13 小时前
C++数据结构和算法代码模板总结——算法部分
数据结构·c++
小猫咪怎么会有坏心思呢14 小时前
华为OD机考 - 水仙花数 Ⅰ(2025B卷 100分)
数据结构·链表·华为od
hn小菜鸡14 小时前
LeetCode 1356.根据数字二进制下1的数目排序
数据结构·算法·leetcode
SuperCandyXu17 小时前
leetcode2368. 受限条件下可到达节点的数目-medium
数据结构·c++·算法·leetcode