AVL树(平衡二叉搜索树)

你真的很美丽为什么常说自己的外貌丑

文章目录

  • 什么是AVL树
  • AVL树的实现
    • AVL树节点的定义
    • avl树的插入
  • 旋转维护avl树
    • 1情况 右单选
    • 2情况 左单旋
    • 3 右左双旋(先右单旋然后再左单旋)
    • 4 新节点插入较高左子树的右侧:左右(先左单旋再右单旋)
  • 代码总结

什么是AVL树

首先先说一下AVL名字的由来,AVL的名字来自于它的发明者G. M. Adelson-Velsky 和E. M. Landis,也就是这两位来自俄罗斯的大佬,那么我们为什么要学习AVL树呢?
那是因为我们平常用的二叉搜索树在一些极端情况下它的查找会变成o(N)的时间复杂度,也就是斜二叉树的情况,他们发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
那么avl树和普通的树比起来有什么特点呢?

  1. 首先avl树相比二叉搜索树加入了平衡因子,平衡因子的作用是为了记录以该节点为根的左右子树的高度差,假设这个平衡因子是bf那么bf=hright(右子树的高度)-hleft(左子树的高度)注意这里没有绝对值。
  2. avl树的左右子树也都是avl树
  3. avl树的左右高度差不超过1.

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

AVL树的实现

好的那么听完上面的介绍相信大家对于avl树已经有了一个初步的认识了那么我们现在来实现一下AVL树

AVL树节点的定义

首先呢想实现AVL树我们需要先定义出avl树的结点需要包含的元素,这里我们可以用一个类来实现,当然了在c++中struct被优化了所以可以直接写一个struct类它默认就是public代码如下

template<class K, class V>
struct AVLTreeNode
{
	pair<K, V> _kv;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	int _bf;  // balance factor

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

这里呢我们可以看到这里面包含的跟二叉树不同地方就在于这里面首先包含了左右子树之外还包含了父指针,以及_bf(平衡因子)这里的关键就是这个bf它将成为这个avl树的点睛之笔。

avl树的插入

avl树其实就是在二叉搜索树的基础上引入了平衡因子我们上面提到了,avl树通过平衡因子不断对自身结构进行调整,使得自身始终保持在一个平衡的状态,因此,avl树的关键其实就是插入的过程,因为插入的过程中我们要不断的对自身结构进行因此avl树的插入也可以分为两步

  1. 按照二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子

那么代码如下:

首先像二叉树那样插入
	bool Insert(const pair<K, V>& kv,vector<int>&qu)
	{
		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->_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
		{
			parent->_left = cur;
		}

		cur->_parent = parent;

		// ... 控制平衡
		// 更新平衡因子
		while (parent)
		{
			if (cur == parent->_left)
			{
				parent->_bf--;
			}
			else if (cur == parent->_right)
			{
				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(cur);
				}
				if (parent->_bf == 2&&cur->_bf==1)
				{
					RotateL(cur);
				}
				if (parent->_bf == -2 && cur->_bf == 1)
				{
					RotateLR(cur);
				}
				if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(cur);
				}
			}
			else
			{
				cout << "+++++++++" << endl;
				for (auto c : qu)
				{
					cout << c << ' ';
				}
				cout << endl;
				assert(false);
			}
		}
		return true;
	}

这里呢我其实写了两部第一步是将其插入进去第二步是更新节点的平衡因子,在这里我门要知道每个节点平衡因子的计算是,bf=rhight-lhight,因此呢当新的节点插入到该节点左边的子树的时候呢bf需要减减,当插入右边的子树的时候bf会加加。因此呢,当新节点插入后原来的平衡很可能会被打破,那么怎样才算打破呢?我们上面提到左右子树的高度差要保持为1,因此当该节点的平衡因子变为2或者-2的时候就意味着平衡被打破了,我们就需要对该平衡树进行结构上的维护好的那么接下来就是本节的重点关于avl树的维护。

旋转维护avl树

那么当我们插入一个点后这个节点的平衡因子变为多少需要我们进行更新呢?其实就是2或者-2,那么这时又出现了几种情况。

1. 新节点插入到了较高左子树的左侧——左左
2. 新节点插入到了较高右子树的右侧——右右
3. 新节点插入到了较高右子树的左侧——右左
4. 新节点插入到了较高左子树的右侧——左右

那么这四种方式对应的旋转也是不一样的。
下面我用上面的1,2,3,4表示对应情况出现的旋转方式

1情况 右单选

我们看一下下面这个图片
AVL树(平衡二叉搜索树)_第1张图片
这张图片呢我们可以很清楚的知道这里已经失衡了,那么右单选的结果是什么呢?
请看下图
AVL树(平衡二叉搜索树)_第2张图片
这副图就是右单选后的情景了那么这只是一种情况假如说30 那个节点还有一个右节点呢?我们画一下
AVL树(平衡二叉搜索树)_第3张图片
从上面呢我们是可以看到一些规律的首先右单旋就是旋转中心(此处是30)的右子树变为其父节点的左子树,其父子树变为其右子树,然后呢从而实现了30和60之间的旋转交换那么请看旋转后,变成了平衡二叉树了。
代码实现

void RotateR(Node* cur)
	{
		Node* parent = cur->_parent;
		Node* pparent = parent->_parent;
		parent->_left = cur->_right;
		if (cur->_right != nullptr)
		{
			cur->_right->_parent = parent;
		}
		cur->_right = parent;
		//cur->_parent = parent->_parent;
		parent->_parent = cur;
		if (pparent == nullptr)
		{
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			if (pparent->_left == parent)
			{
				pparent->_left = cur;
			}
			else
			{
				pparent->_right = cur;
			}
			cur->_parent = pparent;
		}
		parent->_bf = 0;
		cur->_bf = 0;
	}

2情况 左单旋

那么有了上面的基础我们也知道了左单选,其实就是旋转中心的左子树变为其父节点的右子树然后其父节点变为其左子树从而实现了位置交换那么我贴一张概念图方便大家理解

AVL树(平衡二叉搜索树)_第4张图片
也就是这样的。那么到代码里应该怎样实现呢?

void RotateL(Node* cur)
	{
		Node* parent = cur->_parent;
		parent->_right = cur->_left;
		if (cur->_left != nullptr)
		{
			cur->_left->_parent = parent;
		}
		cur->_left = parent;
		Node* pparent = parent->_parent;
		parent->_parent = cur;
		if (pparent == nullptr)
		{
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			if (pparent->_left == parent)
			{
				pparent->_left = cur;
			}
			else
			{
				pparent->_right = cur;
			}
			cur->_parent = pparent;
		}
		cur->_bf = 0;
		parent->_bf = 0;
	}

3 右左双旋(先右单旋然后再左单旋)

AVL树(平衡二叉搜索树)_第5张图片
这里呢先给大家看一下概念图,右做双旋的情况比较复杂,因为他右单旋和左单旋的点是不一样的换句话说就是,右单选是为了接下来可以左单选我们可以把双旋看成一个曲线,单旋看成直线(这里的曲线和直线是指他的bf的正负单旋是旋转的点正负一致双旋刚好相反)所以呢我们先进行一个右单旋从而使我们可以进行单旋,代码如下

void RotateRL(Node*cur)
	{
		Node* parent = cur->_parent;
		Node* curleft = cur->_left;
		int bf = curleft->_bf;
		RotateR(curleft);
		RotateL(cur->_parent);
		if (bf == 0)
		{
			cur->_bf = 0;
			curleft->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == 1)
		{
			cur->_bf = 0;
			curleft->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			cur->_bf = 1;
			curleft->_bf = 0;
			parent->_bf = 0;
		}
	}

另外就是我们在进行完旋转后也需要对其对应的bf因子进行更新更新的规律就是上面写的if代码中有注释的解释我们也要分不同的情况

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

AVL树(平衡二叉搜索树)_第6张图片
有了上面的描述这里也是同样的道理的我把代码给大家贴出来

void RotateLR(Node* cur)
	{
		Node* parent = cur->_parent;
		Node* curright = cur->_right;
		int bf = curright->_bf;
		RotateL(curright);
		RotateR(cur->_parent);
		if (bf == 0)
		{
			parent->_bf = 0;
			cur->_bf = 0;
			curright->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 1;
			cur->_bf = 0;
			curright->_bf = 0;
		}
		else if (bf == 1)
		{
			parent->_bf = 0;
			cur->_bf = -1;
			curright->_bf = 0;
		}
	}

代码总结

下面是整个插入的代码

首先是头文件
#pragma once
#include
#include
#include
using namespace std;

template<class K, class V>
struct AVLTreeNode
{
	pair<K, V> _kv;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	int _bf;  // balance factor

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

template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	bool Insert(const pair<K, V>& kv,vector<int>&qu)
	{
		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->_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
		{
			parent->_left = cur;
		}

		cur->_parent = parent;

		// ... 控制平衡
		// 更新平衡因子
		while (parent)
		{
			if (cur == parent->_left)
			{
				parent->_bf--;
			}
			else if (cur == parent->_right)
			{
				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(cur);
				}
				if (parent->_bf == 2&&cur->_bf==1)
				{
					RotateL(cur);
				}
				if (parent->_bf == -2 && cur->_bf == 1)
				{
					RotateLR(cur);
				}
				if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(cur);
				}
			}
			else
			{
				cout << "+++++++++" << endl;
				for (auto c : qu)
				{
					cout << c << ' ';
				}
				cout << endl;
				assert(false);
			}
		}
		return true;
	}
	void RotateL(Node* cur)
	{
		Node* parent = cur->_parent;
		parent->_right = cur->_left;
		if (cur->_left != nullptr)
		{
			cur->_left->_parent = parent;
		}
		cur->_left = parent;
		Node* pparent = parent->_parent;
		parent->_parent = cur;
		if (pparent == nullptr)
		{
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			if (pparent->_left == parent)
			{
				pparent->_left = cur;
			}
			else
			{
				pparent->_right = cur;
			}
			cur->_parent = pparent;
		}
		cur->_bf = 0;
		parent->_bf = 0;
	}
	void RotateR(Node* cur)
	{
		Node* parent = cur->_parent;
		Node* pparent = parent->_parent;
		parent->_left = cur->_right;
		if (cur->_right != nullptr)
		{
			cur->_right->_parent = parent;
		}
		cur->_right = parent;
		//cur->_parent = parent->_parent;
		parent->_parent = cur;
		if (pparent == nullptr)
		{
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			if (pparent->_left == parent)
			{
				pparent->_left = cur;
			}
			else
			{
				pparent->_right = cur;
			}
			cur->_parent = pparent;
		}
		parent->_bf = 0;
		cur->_bf = 0;
	}
	void RotateRL(Node*cur)
	{
		Node* parent = cur->_parent;
		Node* curleft = cur->_left;
		int bf = curleft->_bf;
		RotateR(curleft);
		RotateL(cur->_parent);
		if (bf == 0)
		{
			cur->_bf = 0;
			curleft->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == 1)
		{
			cur->_bf = 0;
			curleft->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			cur->_bf = 1;
			curleft->_bf = 0;
			parent->_bf = 0;
		}
	}
	void RotateLR(Node* cur)
	{
		Node* parent = cur->_parent;
		Node* curright = cur->_right;
		int bf = curright->_bf;
		RotateL(curright);
		RotateR(cur->_parent);
		if (bf == 0)
		{
			parent->_bf = 0;
			cur->_bf = 0;
			curright->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 1;
			cur->_bf = 0;
			curright->_bf = 0;
		}
		else if (bf == 1)
		{
			parent->_bf = 0;
			cur->_bf = -1;
			curright->_bf = 0;
		}
	}
	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 IsBalance()
	{
		return IsBalance(_root);
	}

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

		int leftHight = Height(root->_left);
		int rightHight = Height(root->_right);

		if (rightHight - leftHight != root->_bf)
		{
			cout << "平衡因子异常:" << root->_kv.first << "->" << root->_bf << endl;
			return false;
		}

		return abs(rightHight - leftHight) < 2
			&& IsBalance(root->_left)
			&& IsBalance(root->_right);
	}
private:
	Node* _root = nullptr;
};
然后是cpp测试文件这个文件主要就是测试一下功能
#include"AVL2.h"
int main()
{
	AVLTree<int, int>avl;
	int num[10] = { 1,2,3,4,5,6,7,8,9 };
	for (auto c : num)
	{
		avl.Insert({ c,c });
	}

	return 0;
}

你可能感兴趣的:(数据结构,c++,c++,数据结构)