【数据结构】-----AVL树

目录

前言

一、What is it? 

 二、How achieve it?

Ⅰ、 结点类定义

Ⅱ、实现

插入

情况一:为0

 情况二:1/-1

情况三:2 /-2

旋转 (重难点)

 右单旋

左单旋

左右双旋

右左双旋

 AVL树验证

 ①验证中序遍历

②验证平衡

 ③样例

查找 

大小

Ⅲ、完整实现代码

三、性能


前言

前面二叉搜索树一文中末尾提到过AVL树能解决因单支树导致性能下降的问题,是优化二叉搜索树的一种手段,接下来看看呗!

一、What is it? 

 AVL树也叫二叉平衡搜索树,是在二叉搜索树的基础上引入了平衡因子,具有以下的特点:

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

形如:

如果一棵二叉搜索树的高度是平衡的,他就是AVL树。若它有n个结点,其高度可保持在logN。

 二、How achieve it?

Ⅰ、 结点类定义

这里的直接采用KV模型,每一个元素是pair,同时采用三叉链的结构,因为插入过程中有的情况需要向上调整,找祖先方便!

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;  // 平衡因子

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

Ⅱ、实现

插入

这里设平衡因子=右子树-左子树

规则:

1.首先依旧按照二叉搜索树的规则插入。小的放在左子树,大的放在右子树

2.插入新结点后,平衡性可能遭到破坏,此时需要更新祖先结点的平衡因子,更新规则如下:

  • 插入到父亲结点左侧,父亲结点的平衡因子-1即可
  • 插入到父亲结点右侧,父亲结点的平衡因子+1即可

更新完成后,此时父亲结点的平衡因子可能存在三种情况0,1/-1,2/-2

情况一:为0

这种情况,说明在没更新前父节点的平衡因子为-1/1,插入后变成了0,此时,父亲结点所在的子树高度不变,不需在继续向上更新,插入成功!

 情况二:1/-1

这种情况说明没更新前父节点的平衡因子为0,插入后变成了1/-1,此时父亲所在子树的高度增加,需要继续向上更新,直到为0

向上更新的逻辑:

cur=parent;
parent=parent->_parent;
情况三:2 /-2

当到这种情况时,高度差大于1,严重违反了AVL树的特性,需要旋转处理。

旋转又分为四种情况的旋转:

  • parent平衡因子为-2,cur平衡因子为-1,右单旋
  • parent平衡因子为2,cur平衡因子为1,左单旋
  • parent平衡因子为-2,cur平衡因子为1,先左旋再右旋,左右双旋
  • parent平衡因子为2,cur平衡因子为-1,先右旋再左旋,右左双旋

注意:cur的平衡因子一定不为0,即cur不是新增结点,因为这种情况是一步一步向上更新而来,会先经过情况二。且进行旋转处理后就不需要在向上更新了,平衡了。

上述过程的代码如下:

bool Insert(const pair<K, V>& kv)
{
	if (_root == nullptr)
	{
		_root = new Node(kv);
		return true;
	}
	Node* parent = nullptr;
	Node* cur = _root;
	//找位置插入,与二叉搜索树规则一样,用Key比较
	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->_left)
			parent->_bf--;
		else
			parent->_bf++;

		//为0,直接结束
		if (parent->_bf == 0)
			break;
		//为1/-1,继续向上调整
		else if (parent->_bf == 1 || parent->_bf == -1)
		{
			cur = parent;
			parent = parent->_parent;
		}
		//为2/-2,分四种情况旋转
		else if (parent->_bf == 2 || parent->_bf == -2)
		{
			//第一种,左子树高,插入左子树的左侧,右单旋
			if (parent->_bf == -2 && cur->_bf == -1)
			{
				RotaRTree(parent);
			}
			//第二种,右子树高,插入右子树的右侧,左单旋
			else if (parent->_bf == 2 && cur->_bf == 1)
			{
				RotaLTree(parent);
			}
			//第三种,左子树高,插入左子树的右侧,左右单旋,先左单旋变成单纯左边高,在右单旋
			else if (parent->_bf == -2 && cur->_bf == 1)
			{
				RotaLRTree(parent);
			}
			//第四种,右子树高,插入右子树的左侧,右左单旋,先右旋变成单纯的右边高,在左旋
			else if (parent->_bf == 2 && cur->_bf == -1)
			{
				RotaRLTree(parent);
			}
		}
		//不可能存在的情况
		else
			assert(false);
		break;
	}
	return true;
}

旋转 (重难点)

 右单旋

右单旋,这种情况原来的左子树比右子树高,新增结点在左子树的最左侧的结点后,即左左。

从上图不难分析出,右单旋的总体逻辑就是:

以parent为旋转点,parent的左子树变成了cur的右子树,cur的右子树变成parent。

注意:

1.cur结点的右子树可能不存在,为空,如h=0时。

2.parent结点可能是根结点,也可能是子树。

  • 为根节点,要更新根节点
  • 为子树,可能是左也可能是右,所以需要保存parent的parent

大家可能会有疑问,在4后新增不行吗?行,只不过变成了双旋的情况(下文都有提到,别急,现在是右单旋的情况)

注意:抽象图的存在是因为可能性很多很多,即h可以取0,1,2,……,n。上述h=1仅仅只是举例。

代码实现如下:

因为旋转点是parent,所以只传入parent。

void RotaRTree(Node* parent)
{
	Node* subL = parent->_left;//父亲的左孩子
	Node* subLR = subL->_right;//父亲左的右孩子
	Node* pparent = parent->_parent;//当前结点可能是子树,要保存其父亲结点

	//父亲左子树变成父亲左孩子的右子树
	parent->_left = subLR;
	if (subLR)//可能为空
		subLR->_parent = parent;

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

	//parent结点为根结点
	if (parent == _root)
	{
		_root = subL;
		_root->_parent = nullptr;
	}
	//为子树
	else
	{
		if (pparent->_left == parent)
			pparent->_left = subL;
		else
			pparent->_right = subL;
		subL->_parent = pparent;
	}
	//更新平衡因子
	parent->_bf = subL->_bf = 0;
}
左单旋

与右单旋恰恰相反,原来的右子树比左子树高,新增结点在右子树的最右侧的结点。即右右

从上图不难分析出,左单旋的总体逻辑与右单旋相反,就是:

以parent为旋转点,parent的右子树变成了cur的左子树,cur的左子树变成parent。

还是注意两个问题:

1.subR(cur)可能为空

2.parent可能为根节点,也可能是子树。

代码实现如下:

void RotaLTree(Node* parent)
{
	Node* subR = parent->_right;//右孩子
	Node* subRL = subR->_left;//右孩子的左子树
	Node* pparent = parent->_parent;
	//父亲右子树变成右孩子的左子树
	parent->_right = subRL;
	if (subRL)
		subRL->_parent = parent;
	subR->_left = parent;
	parent->_parent = subR;
	if (parent == _root)
	{
		_root = subR;
		_root->_parent = nullptr;
	}
	else
	{
		if (pparent->_left == parent)
			pparent->_left = subR;
		else
			pparent->_right = subR;
		subR->_parent = pparent;
	}
	parent->_bf = subR->_bf = 0;
}
左右双旋

先左单旋,在右单旋。这种情况原来的左子树比右子树高新增结点在左子树的最右侧的结点后。即左右。

从上图可以分析出,左右双旋的整体逻辑是:

当更新至parent的平衡因子为-2,cur平衡因子1时:

1.以subL为旋转点进行左单旋,即以subL作为参数,调用左旋函数即可。

2.以subLR为旋转点进行右单旋,即以subLR作为参数,直接调用右旋函数即可。

3.最后在更新平衡因子。

值得注意的是旋转完毕后的平衡因子更新有点讲究,会随着subLR起始的平衡因子分三种情况:(提醒:这里的起始平衡因子的意思是,插入新增结点后的平衡因子)

  • subLR起始平衡因子为0时,左右双旋后,subLR平衡因子为0,subL为0,parent为0。(这种情况为subLR自己是新增结点,即h=0的情况,如下)

  • subLR起始平衡因子为-1时,左右双旋后,subLR平衡因子为0,subL为0,parent为1。(这种情况是插入到左子树最右结点的左侧,如上述的抽象图)
  • subLR起始平衡因子为1时,左右双旋后,subLR平衡因子为0,subL为-1,parent为0。(这种情况是插入到左子树最右结点的右侧,如下

代码实现如下:

void RotaLRTree(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	int bf = subLR->_bf;//提前保存subLR的平衡因子,因为左右旋后会变

	//先左旋
	RotaLTree(parent->_left);//subL为旋转点,如上述的图中的3结点
	//再右旋
	RotaRTree(parent);//parent为旋转点,如图中的5结点

	//更新平衡因子
	if (bf == 0)//subLR为新插入的结点
	{
		subL->_bf = subLR->_bf = parent->_bf = 0;
	}
	else if (bf == -1)//新插入的结点在subLR的左边
	{
		subLR->_bf = 0;
		subL->_bf = 0;
		parent->_bf = 1;
	}
	else if (bf == 1)//新插入的结点在subLR的右边
	{
		subLR->_bf = 0;
		subL->_bf = -1;
		parent->_bf = 0;
	}
	else   //不可能的情况
		assert(false);
}
右左双旋

 与左右相反。先右单旋,在左单旋。这种情况原来的右子树比左子树高新增结点在右子树的最左侧的结点后。即右左。

从上图可以分析出,左右双旋的整体逻辑是:

当更新至parent的平衡因子为2,cur平衡因子-1时:

1.以subR为旋转点进行右单旋,即以subR作为参数,调用右旋函数即可。

2.以subRL为旋转点进行左单旋,即以subRL作为参数,调用左旋函数即可。

3.最后在更新平衡因子。

同样平衡因子的更新也很有讲究,会随着subRL起始的平衡因子分三种情况:(提醒:这里的起始平衡因子的意思是,插入新增结点后的平衡因子)

  • subRL起始平衡因子为1时,左右双旋后,subRL平衡因子为0,subR为0,parent为-1。(这种情况是插入到右子树最左结点的右侧,如上图所示
  • subRL起始平衡因子为-1时,左右双旋后,subRL平衡因子为0,subR为1,parent为0。(这种情况是插入到右子树最左结点的左侧,如下图所示

  • subRL起始平衡因子为0时,左右双旋后,subRL平衡因子为0,subR为0,parent为0。(这种情况是subRL为新增结点,如下图所示

代码实现如下:

void RotaRLTree(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	int bf = subRL->_bf;//提前保存subRL的平衡因子,因为左右旋后会变

	//先右旋
	RotaRTree(parent->_right);//subR为旋转点,如图中的5结点
	//先左旋
	RotaLTree(parent);//parent为旋转点,如上述的图中的3结点


	//更新平衡因子
	if (bf == 0)//subLR为新插入的结点
	{
		subR->_bf = subRL->_bf = parent->_bf = 0;
	}
	else if (bf == -1)//新插入的结点在subLR的左边
	{
		subRL->_bf = 0;
		subR->_bf = 1;
		parent->_bf = 0;
	}
	else if (bf == 1)//新插入的结点在subLR的右边
	{
		subRL->_bf = 0;
		subR->_bf = 0;
		parent->_bf = -1;
	}
	else   //不可能的情况
		assert(false);
}

 AVL树验证

 ①验证中序遍历

若中序遍历得到的是一个有序序列,说明为二叉搜索树。

中序遍历和二叉搜索树写法类似

public:
    void Inoder()
    {
	    _Inoder(_root);
	    cout << endl;
    }
private:
    Node *_root;//私有成员变量
	void _Inoder(Node* root)
	{
		if (root == nullptr)
			return;
		_Inoder(root->_left);
		cout << root->kv.first<< " ";
		_Inoder(root->_right);
	}
②验证平衡
  • 结点子树的高度差不超过1
  • 结点的平衡因子是否正确
	int _Height(Node* root)
	{
		if (root == nullptr)
			return 0;

		return max(_Height(root->_left), _Height(root->_right)) + 1;
	}
	bool _IsBlance(Node* root)
	{
		if (root == nullptr)
			return true;
		//1.结点子树高度差不超过1
		int lefthight = _Height(root->_left);
		int righthight = _Height(root->_right);
		if (abs(lefthight - righthight) >= 2)
		{
			cout << root->_kv.first << endl;//输出不平衡结点
			return false;
		}
		//2.结点平衡因子是否正确
		if (righthight - lefthight != root->_bf)
		{
			cout << root->_kv.first << endl;
			return false;
		}
		//递归左右子树,检查子树是否为AVL树
		return 	_IsBlance(root->_left) && _IsBlance(root->_right);
	}
 ③样例
void Test()
{
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	AVLTree<int,int> v;
	for (auto e : a)
	{
		v.Insert({e,e});//每个元素是一个pair
	}
	v.Inoder();

	if (v.IsBlance())
	{
		cout << "Blance?:平衡" << endl;
	}
	else
	{
		cout << "Blance?:不平衡" << endl;
	}

}

结果

可以看到,小编的是平衡的,若无法通过该样例,说明出错,但代码量又很大,直接找不方便,这里有个小技巧,每插入一个就检查是否平衡!这样能很快定位错误的点!

	for (auto e : a)
	{
		v.Insert({e,e});
		cout << "Insert:" << e << "->" << v.IsBlance() << endl;
	}

查找 

和二叉搜索树类似。只不过这里的元素是pair键值对。

	Node* Find(const K>& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (key < cur->_kv.first)
				cur = cur->_left;
			else if (key > cur->_kv.first)
				cur = cur->_right;
			else
				return cur;

		}
		return nullptr;
	}

大小

public:
	int size()
	{
		return _size(_root);
	}
private:
	Node* _root=nullptr;
	int _size(Node* root)
	{
		if (root == nullptr)
			return 0;

		return _size(root->_left) + _size(root->_right) + 1;
	}

Ⅲ、完整实现代码

#pragma once
#include<assert.h>
#include<iostream>
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:
	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return 0;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		//找位置插入,与二叉搜索树规则一样,用Key比较
		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->_left)
				parent->_bf--;
			else
				parent->_bf++;

			//为0,直接结束
			if (parent->_bf == 0)
				break;
			//为1/-1,继续向上调整
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				cur = parent;
				parent = parent->_parent;
			}
			//为2/-2,分四种情况旋转
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				//第一种,左子树高,插入左子树的左侧,右单旋
				if (parent->_bf == -2 && cur->_bf == -1)
				{
					RotaRTree(parent);
				}
				//第二种,右子树高,插入右子树的右侧,左单旋
				else if (parent->_bf == 2 && cur->_bf == 1)
				{
					RotaLTree(parent);
				}
				//第三种,左子树高,插入左子树的右侧,左右单旋,先左单旋变成单纯左边高,在右单旋
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
					RotaLRTree(parent);
				}
				//第四种,右子树高,插入右子树的左侧,右左单旋,先右旋变成单纯的右边高,在左旋
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotaRLTree(parent);
				}
				break;
			}
			//不可能存在的情况
			else
				assert(false);
		}
		return true;
	}
	bool IsBlance()
	{
		return _IsBlance(_root);
	}
	void Inoder()
	{
		_Inoder(_root);
		cout << endl;
	}
	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (key < cur->_kv.first)
				cur = cur->_left;
			else if (key > cur->_kv.first)
				cur = cur->_right;
			else
				return cur;

		}
		return nullptr;
	}
	int size()
	{
		return _size(_root);
	}
private:
	Node* _root=nullptr;
	int _size(Node* root)
	{
		if (root == nullptr)
			return 0;

		return _size(root->_left) + _size(root->_right) + 1;
	}
	void RotaRTree(Node* parent)
	{
		Node* subL = parent->_left;//父亲的左孩子
		Node* subLR = subL->_right;//父亲左的右孩子
		Node* pparent = parent->_parent;//当前结点可能是子树,要保存其父亲结点

		//父亲左子树变成父亲左孩子的右子树
		parent->_left = subLR;
		if (subLR)//可能为空
			subLR->_parent = parent;

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

		//parent结点为根结点
		if (parent == _root)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		//为子树
		else
		{
			if (pparent->_left == parent)
				pparent->_left = subL;
			else
				pparent->_right = subL;
			subL->_parent = pparent;
		}
		//更新平衡因子
		parent->_bf = subL->_bf = 0;
	}
	void RotaLTree(Node* parent)
	{
		Node* subR = parent->_right;//右孩子
		Node* subRL = subR->_left;//右孩子的左子树
		Node* pparent = parent->_parent;
		//父亲右子树变成右孩子的左子树
		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;
		subR->_left = parent;
		parent->_parent = subR;
		if (parent == _root)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			if (pparent->_left == parent)
				pparent->_left = subR;
			else
				pparent->_right = subR;
			subR->_parent = pparent;
		}
		parent->_bf = subR->_bf = 0;
	}
	void RotaLRTree(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;//提前保存subLR的平衡因子,因为左右旋后会变

		//先左旋
		RotaLTree(parent->_left);//subL为旋转点,如上述的图中的3结点
		//再右旋
		RotaRTree(parent);//parent为旋转点,如图中的5结点

		//更新平衡因子
		if (bf == 0)//subLR为新插入的结点
		{
			subL->_bf = subLR->_bf = parent->_bf = 0;
		}
		else if (bf == -1)//新插入的结点在subLR的左边
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == 1)//新插入的结点在subLR的右边
		{
			subLR->_bf = 0;
			subL->_bf = -1;
			parent->_bf = 0;
		}
		else   //不可能的情况
			assert(false);
	}
	void RotaRLTree(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;//提前保存subRL的平衡因子,因为左右旋后会变

		//先右旋
		RotaRTree(parent->_right);//subR为旋转点,如图中的5结点
		//先左旋
		RotaLTree(parent);//parent为旋转点,如上述的图中的3结点


		//更新平衡因子
		if (bf == 0)//subLR为新插入的结点
		{
			subR->_bf = subRL->_bf = parent->_bf = 0;
		}
		else if (bf == -1)//新插入的结点在subLR的左边
		{
			subRL->_bf = 0;
			subR->_bf = 1;
			parent->_bf = 0;
		}
		else if (bf == 1)//新插入的结点在subLR的右边
		{
			subRL->_bf = 0;
			subR->_bf = 0;
			parent->_bf = -1;
		}
		else   //不可能的情况
			assert(false);
	}
	void _Inoder(Node* root)
	{
		if (root == nullptr)
			return;
		_Inoder(root->_left);
		cout << root->_kv.first << " ";
		_Inoder(root->_right);
	}
	//计算当子树的高度
	int _Height(Node* root)
	{
		if (root == nullptr)
			return 0;

		return max(_Height(root->_left), _Height(root->_right)) + 1;
	}
	bool _IsBlance(Node* root)
	{
		if (root == nullptr)
			return true;
		//1.结点子树高度差不超过1
		int lefthight = _Height(root->_left);
		int righthight = _Height(root->_right);
		if (abs(lefthight - righthight) >= 2)
		{
			cout << root->_kv.first << endl;//输出不平衡结点
			return false;
		}
		//2.结点平衡因子是否正确
		if (righthight - lefthight != root->_bf)
		{
			cout << root->_kv.first << endl;
			return false;
		}
		//递归左右子树,检查子树是否为AVL树
		return 	_IsBlance(root->_left) && _IsBlance(root->_right);
	}
};

void Test()
{
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	AVLTree<int,int> v;
	for (auto e : a)
	{
		v.Insert({e,e});
		cout << "Insert:" << e << "->" << v.IsBlance() << endl;
	}
	v.Inoder();

	if (v.IsBlance())
	{
		cout << "Blance?:平衡" << endl;
	}
	else
	{
		cout << "Blance?:不平衡" << endl;
	}
	cout << v.size() << endl;
}


三、性能

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

好了,今天的分享就到这里,如果觉得对你有所帮助,欢迎三连 !!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值