剑起霜华——平衡二叉树(AVL树 )精讲

晨光熹微------AVL树

AVL树是基于二叉搜索树规定相关的插入、删除方式实现的一种高效的二叉搜索树,通过引入平衡因子和旋转的概念,使得整棵树趋于满二叉树,这样大大提高了查找效率(时间复杂度logN)。

AVL树的特点:

1.每一个节点的左子树上的所有节点小于该节点存储的数据,右子树上所有节点存储的数据都大于该节点。(大小关系也可取完全相反:"左大右小")

2.中序遍历整棵树得到的数据是有序的

3.每个节点的左右子树的高度差不超过1

4.每个节点都有自己的平衡因子,平衡因子的计算方式是:右子树高度-左子树高度

山峦露起------AVL树的判断

已知一棵二叉树的根指针,并且这棵树为二叉搜索树,怎么判断它为平衡二叉树:

1.左右子树高度差的绝对值小于1

2.每个节点的左右子树为平衡二叉树

cpp 复制代码
/* template<class T>
struct node{
  T val;
  node* left;
  node* right;
}
*/
int height(node* root)
{
    if(root=nullptr)return 0;
	int Hleft = height(root->_left);
	int Hright = height(root->_right);
	return Hleft > Hright ? Hleft+1: Hright+1;
}
bool isAVLTree(node* root)
{
	if (root == nullptr)return true;
	int Hleft = height(root->_left);
	int Hright = height(root->_right);
	if (abs(Hleft - Hright) > 1)return false;
	return isAVLTree(root->_left)&&isAVLTree(root->_right);

}

拨云见雾------AVL树结构的设计

AVL树是由一个个节点构成的,每存储一个数据就要动态内存开辟一个节点,所以首先来设计其节点,我们用一个结构体来封装:

cpp 复制代码
template<class K, class V>
struct AVLTnode
{
public:
	pair<K, V>  _kv;
	AVLTnode* _parent;
	AVLTnode* _left;
	AVLTnode* _right;
	int _bf;//平衡因子
	AVLTnode(K key1, V val1)
		:_kv(key1, val1), _parent(nullptr),
		_left(nullptr), _right(nullptr), _bf(0)
	{

	}
};

结构中成员变量的用途:

1.pair<k,V>:用来存储数据的一个键值对

2._parent :用来访问双亲节点,方便后期旋转和更新平衡因子的操作

3._left :用来访问左孩子节点,方便左孩子的插入

4._right :用来访问右孩子节点,方便右孩子的插入

5._bf :平衡因子(balance factor),是该节点右子树和左子树高度的差值只能是

(-1,0,1)如果不是这三个值,就要基于该节点为根的子树进行旋转操作

6.构造函数 :方便new一个节点操作

接下来用类封装AVL树:

cpp 复制代码
template<class K, class V>
class AVLTree
{
	typedef AVLTnode<K, V> node;
public:
	
private:
	node* _root=nullptr;
	
};

在这个类中实现所有的相关方法:

又见春山------AVL树的查找:

给定一个指针标记当前"遍历"的节点,如果当前节点的K比指定待查找节点的K较小,指针下一步走到当前节点的左孩子,如果当前节点的K比指定待查找节点的K较大,指针下一步走到当前节点的右孩子,如果相等,返回当前节点的地址:

cpp 复制代码
node* find(K key)//按key查找
{
	node* cur = _root;
	while (cur)
	{
		if (cur->_kv.first > key)cur = cur->_left;
		else if (cur->_kv.first < key)cur = cur->_right;
		else return cur;
	}
	return nullptr;
}

剑开天门------AVL树的插入:

第一式:找到适合新节点插入的位置

首先利用快慢指针:快指针用于如查找方式的遍历,慢指针用于标记快指针指向节点双亲结点,,如果当前节点的K比指定待查找节点的K较小,快指针下一步走到当前节点的左孩子,如果当前节点的K比指定待查找节点的K较大,快指针下一步走到当前节点的右孩子,只不过由于本次实现的树是只允许插入数据的K在树中唯一,所以当快指针指向节点的K和待插入节点的k相等时要停止插入,直到快指针遍历到nullptr.

第二式:插入节点并更新平衡因子

new一个节点,通过修改该节点的双亲指针和慢指针指向节点的孩子指针来进行链接,小细节是要:判断链接的是左孩子还是右孩子

还是使用快慢指针算法向上其所有祖先更新平衡因子:由于AVL树节点平衡因子的取值只能是0,1,-1,

当叶子节点增加一个右孩子节点,该叶子节点平衡因子+1,该树(子树)高度加1

当叶子节点增加一个左孩子节点,该叶子节点平衡因子-1,该树(子树)高度加1

同理:当一个节点左子树高度加1,该节点平衡因子-1

当一个节点右子树高度加1,该节点平衡因子+1

也就是说每一此新节点的插入就会导致某一部分节点平衡因子+1或-1.那么如此而来向上更新平衡因子之后,所有节点平衡因子的取值为 0 -1/ 1 -2/ 2

因此我们分三种情况讨论:

1.更新之后平衡因子为0,说明之前平衡因子为-1/1,也就是说树(子树)的高度没有变化,停止向上更新平衡因子。(这里是子树示意图,大家可以多画几棵整体的树加强一下理解)

2.更新之后平衡因子为-1/1说明更新之前平衡因子为0,整棵树(子树)的高度发生了改变,继续沿着新插入节点的所有祖先路径更新到根节点

3.更新之后的平衡因子为2/-2,整棵树不再是AVL树,需要对该节点为根节点构成的子树进行旋转操作

旋转操作的两个好处:

1.将整棵树调整成AVL树

2.旋转子树之后,整棵树(其子树)和没插入新节点的高度相同,就不用再向上继续调整其祖先节点的平衡因子了。

代码实现:

cpp 复制代码
bool insert(K key, V val)//插入
{
	node* cur = _root;
	node* parent = nullptr;
	if (cur == nullptr)
	{
		_root = new node(key, val);
		return true;
	}
	else
	{
		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 false;
		}
		//创建并链接新节点
		node* newnode = new node(key, val);
		if (parent->_kv.first < key)
		{
			parent->_right = newnode;
		}
		else
		{
			parent->_left = newnode;
		}
		cur = newnode;
		cur->_parent = parent;
		//更新平衡因子
		while (parent != nullptr)//更新到根节点,根节点的_parent==nullptr
		{
			if (cur == parent->_left)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)
			{
				rotate(parent);
				break;
			}
			else perror("insert fail");
		}
		return true;
	}
}

第三式、旋转

通过分类讨论和总结,插入新节点之后引起其某祖先节点中的平衡因子变成2/-2的只有这四种情况(抽象图如下),当然也对应四种旋转方式来处理这四种情况,由于在旋转过程中形状似的结构的子树内部并没有进行节点链接情况的改变,因此我们将它继续抽象成长方形代表树。

四种情况的判断方式:

右单旋:左子树高,相右旋转:根节点平衡因子-2,根节点左子树根测平衡因子为-1

左单旋:右子树高,向左旋转:根节点平衡因子为2,根节点右子树根平衡因子为1

左右双旋:先左旋,再右旋:根节点平衡因子为-2,根节点左子树根平衡因子为1

右左双旋:先右旋,再左旋:根节点平衡因子为2,根节点右子树根平衡因子为-1

cpp 复制代码
void  rotate(node* root)
{
	//左单旋
	if (root->_bf==2&&root->_right->_bf == 1) rotateL(root);
	//右单旋
	else if (root->_bf==-2&&root->_left->_bf ==-1) rotateR(root);
	//左右双旋
	else if (root->_bf==-2&&root->_left->_bf == 1)rotateLR(root);
	//右左双旋
	else if (root->_bf==2&&root->_right->_bf == -1)rotateRL(root);
	else perror("rotate fail");
}

右单旋:

右单旋细节提醒:

1判断proot

root 可能是根节点,也可能是proot的左孩子或右孩子

2.判断rootR

h为零时抽象树都是空树,rootR=nullptr

3.记得更新平衡因子

只需把root rootR的平衡因子赋值为0即可

cpp 复制代码
//右单旋
void rotateR(node* root)
{
	node* proot = root->_parent;
	node* rootL = root->_left;
	node* rootLR = root->_left->_right;
	//重新选定根节点
	if (proot == nullptr)
	{
		_root = rootL;
		rootL->_parent = nullptr;
	}
	else if (proot->_left == root)
	{
		proot->_left = rootL;
		rootL->_parent = proot;
	}
	else
	{
		proot->_right = rootL;
		rootL->_parent = proot;
	}
	//链接root和rootLR
	root->_left = rootLR;
	if (rootLR)rootLR->_parent = root;
	//链接rootL和root
	rootL->_right = root;
	root->_parent = rootL;
	//更新平衡因子:
	rootL->_bf = root->_bf = 0;
}

左单旋:

左单旋细节提醒

1判断proot

root 可能是根节点,也可能是proot的左孩子或右孩子

2.判断L

h为零时抽象树都是空树,rootL=nullptr

3.记得更新平衡因子

只需把root rootL的平衡因子赋值为0即可

cpp 复制代码
//左单旋
void rotateL(node* root)
{
	node* proot = root->_parent;
	node* rootR = root->_right;
	node* rootRL = rootR->_left;
	//链接proot和rootR
	if (proot == nullptr)
	{
		_root = rootR;
		rootR->_parent= nullptr;
	}
	else if (proot->_left == root)
	{
		proot->_left = rootR;
		rootR->_parent = proot;
	}
	else if (proot->_right == root)
	{
		proot->_right = rootR;
		rootR->_parent = proot;
	}
	//链接root和rootRL
	root->_right = rootRL;
	if(rootRL)rootRL->_parent = root;
	//链接root和rootR
	rootR->_left = root;
	root->_parent = rootR;
	//更新平衡因子
	root->_bf = rootR->_bf = 0;
}

左右双旋:

当插入的数据在a/b子树中时(插入新节点的树的高度为h,未插入新节点的树的高度为h-1)/rootLR就是新插入节点(途中用长方形表示的抽象树全部为空树)

将rootLR作为新的根节点,rootL root分别作为其左右孩子,a分给rootL当其右子树,b分给root当其左子树

由于a/b的情况有三:a:h-1,b:h:a:h,b:h-1;a:0,b:0由上图可知这三种情况会影响rootL,root的最终平衡因子

当然上述比较麻烦的操作也可以通过复用上面已经实现的左单旋,右单旋代码实现:分成两步就是先左旋,再右旋,细节就是根据三种情况来更新三个节点的平衡因子

由上图分析可以得知:最终rootLR的平衡因子为0

情况1:rootL平衡因子为0,root平衡因子为1

情况2:rootL平衡因子为-1,root平衡因子为1

情况3:rootL平衡因子为0,root平衡因子为0

cpp 复制代码
//左右双旋
void rotateLR(node* root)
{
	node* rootL = root->_left;
	node* rootLR = rootL->_right;
	int bf = rootLR->_bf;
	rotateL(rootL);
	rotateR(root);
	if (bf == 1)
	{
		root->_bf = 0;
		rootL->_bf = -1;
		rootLR->_bf = 0;
	}
	else if (bf == -1)
	{
		root->_bf = 1;
		rootL->_bf = 0;
		rootLR->_bf = 0;
	}
	else if (bf == 0)
	{
		root->_bf = 0;
		rootL->_bf = 0;
		rootLR->_bf = 0;
	}
	else {
		perror("rotateLR fail");
	}
}

右左双旋:

当插入的数据在a/b子树中时(插入新节点的树的高度为h,未插入新节点的树的高度为h-1)/rootRL就是新插入节点(途中用长方形表示的抽象树全部为空树)

将rootRL作为新的根节点, root rooR分别作为其左右孩子,a分给root当其右子树,b分给rootR当其左子树

由于a/b的情况有三:a:h-1,b:h:a:h,b:h-1;a:0,b:0由上图可知这三种情况会影响rootL,root的最终平衡因子

由上图分析可以得知:最终rootRL的平衡因子为0

情况1:rootR平衡因子为1,root平衡因子为0

情况2:rootR平衡因子为0,root平衡因子为-1

情况3:rootR平衡因子为0,root平衡因子为0

cpp 复制代码
void rotateRL(node* root)
{
	node* rootR = root->_right;
	node* rootRL = rootR->_left;
	int bf = rootRL->_bf;
	rotateR(rootR);
	rotateL(root);
	if (bf == 0)
	{
		rootR->_bf = 0;
		rootRL->_bf = 0;
		root->_bf = 0;
	}
	else if (bf == 1)
	{
		rootR->_bf = 0;
		rootRL->_bf = 0;
		root->_bf = -1;
	}
	else if (bf == -1)
	{
		rootR->_bf = 1;
		rootRL->_bf = 0;
		root->_bf = 0;
	}
	else
	{
		perror("rotateRL fail");
	}
}

犁庭扫穴------AVL树的其他简单操作

AVL树的删除操作和插入方式类似,所言即是查找更新平衡因子,旋转的组合,这里不做展开。

一些简单的操作比如说树的构造、树的拷贝构造、树的析构、求树的高度、判断这棵树是否是平衡二叉树、树的中序遍历都是很简单,二叉搜索树的一些默认成员函数在上一期也有细讲:二叉搜索树的默认成员函数

下面就直接给到整个AVL树的全部代码和测试代码了:

cpp 复制代码
//AVLTree.h
#pragma once

#include<iostream>
using namespace std;
template<class K, class V>
struct AVLTnode
{
public:
	pair<K, V>  _kv;
	AVLTnode* _parent;
	AVLTnode* _left;
	AVLTnode* _right;
	int _bf;//平衡因子
	AVLTnode(K key1, V val1)
		:_kv(key1, val1), _parent(nullptr),
		_left(nullptr), _right(nullptr), _bf(0)
	{

	}
};
template<class K, class V>
class AVLTree
{
	typedef AVLTnode<K, V> node;
public:
	AVLTree() = default;
	AVLTree(K key, V val) :_root(new node(key, val))
	{

	}
	AVLTree(const AVLTree& t)
	{
		_root = copy(t._root,nullptr);
	}
	~AVLTree()
	{
		destruct(_root);
		_root = nullptr;
	}
	void inOrder()
	{
		_inOrder(_root);
	}
	bool isAVLTree()
	{
		return _isAVLTree(_root);
	}
	int height()
	{
		return _height(_root);
	}
	int size()
	{
		return _size(_root);
	}
	node* find(K key)//按key查找
	{
		node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first > key)cur = cur->_left;
			else if (cur->_kv.first < key)cur = cur->_right;
			else return cur;
		}
		return nullptr;
	}
	bool insert(K key, V val)//插入
	{
		node* cur = _root;
		node* parent = nullptr;
		if (cur == nullptr)
		{
			_root = new node(key, val);
			return true;
		}
		else
		{
			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 false;
			}
			//创建并链接新节点
			node* newnode = new node(key, val);
			if (parent->_kv.first < key)
			{
				parent->_right = newnode;
			}
			else
			{
				parent->_left = newnode;
			}
			cur = newnode;
			cur->_parent = parent;
			//更新平衡因子
			while (parent != nullptr)//更新到根节点,根节点的_parent==nullptr
			{
				if (cur == parent->_left)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)
				{
					rotate(parent);
					break;
				}
				else perror("insert fail");
			}
			return true;
		}
	}
private:
	node* _root=nullptr;
	int _height(node* root)
	{
		if (root == nullptr) return 0;
		int Hleft = _height(root->_left);
		int Hright = _height(root->_right);
		return Hleft > Hright ? Hleft + 1 : Hright + 1;
	}
	bool _isAVLTree(node* root)
	{
		if (root == nullptr)return true;
		int Hleft = _height(root->_left);
		int Hright = _height(root->_right);
		if (abs(Hleft - Hright) > 1)return false;
		if (abs(root->_bf) > 1)return false;
		return _isAVLTree(root->_left) && _isAVLTree(root->_right);
	}
	int _size(node* root)
	{
		if (root == nullptr) return 0;
		return _size(root->_left) + _size(root->_right) + 1;
	}
	void  rotate(node* root)
	{
		//左单旋
		if (root->_bf==2&&root->_right->_bf == 1) rotateL(root);
		//右单旋
		else if (root->_bf==-2&&root->_left->_bf ==-1) rotateR(root);
		//左右双旋
		else if (root->_bf==-2&&root->_left->_bf == 1)rotateLR(root);
		//右左双旋
		else if (root->_bf==2&&root->_right->_bf == -1)rotateRL(root);
		else perror("rotate fail");
	}
	//右单旋
	void rotateR(node* root)
	{
		node* proot = root->_parent;
		node* rootL = root->_left;
		node* rootLR = root->_left->_right;
		//重新选定根节点
		if (proot == nullptr)
		{
			_root = rootL;
			rootL->_parent = nullptr;
		}
		else if (proot->_left == root)
		{
			proot->_left = rootL;
			rootL->_parent = proot;
		}
		else
		{
			proot->_right = rootL;
			rootL->_parent = proot;
		}
		//链接root和rootLR
		root->_left = rootLR;
		if (rootLR)rootLR->_parent = root;
		//链接rootL和root
		rootL->_right = root;
		root->_parent = rootL;
		//更新平衡因子:
		rootL->_bf = root->_bf = 0;
	}
	//左单旋
	void rotateL(node* root)
	{
		node* proot = root->_parent;
		node* rootR = root->_right;
		node* rootRL = rootR->_left;
		//链接proot和rootR
		if (proot == nullptr)
		{
			_root = rootR;
			rootR->_parent= nullptr;
		}
		else if (proot->_left == root)
		{
			proot->_left = rootR;
			rootR->_parent = proot;
		}
		else if (proot->_right == root)
		{
			proot->_right = rootR;
			rootR->_parent = proot;
		}
		//链接root和rootRL
		root->_right = rootRL;
		if(rootRL)rootRL->_parent = root;
		//链接root和rootR
		rootR->_left = root;
		root->_parent = rootR;
		//更新平衡因子
		root->_bf = rootR->_bf = 0;
	}
	//左右双旋
	void rotateLR(node* root)
	{
		node* rootL = root->_left;
		node* rootLR = rootL->_right;
		int bf = rootLR->_bf;
		rotateL(rootL);
		rotateR(root);
		if (bf == 1)
		{
			root->_bf = 0;
			rootL->_bf = -1;
			rootLR->_bf = 0;
		}
		else if (bf == -1)
		{
			root->_bf = 1;
			rootL->_bf = 0;
			rootLR->_bf = 0;
		}
		else if (bf == 0)
		{
			root->_bf = 0;
			rootL->_bf = 0;
			rootLR->_bf = 0;
		}
		else {
			perror("rotateLR fail");
		}
	}
	void rotateRL(node* root)
	{
		node* rootR = root->_right;
		node* rootRL = rootR->_left;
		int bf = rootRL->_bf;
		rotateR(rootR);
		rotateL(root);
		if (bf == 0)
		{
			rootR->_bf = 0;
			rootRL->_bf = 0;
			root->_bf = 0;
		}
		else if (bf == 1)
		{
			rootR->_bf = 0;
			rootRL->_bf = 0;
			root->_bf = -1;
		}
		else if (bf == -1)
		{
			rootR->_bf = 1;
			rootRL->_bf = 0;
			root->_bf = 0;
		}
		else
		{
			perror("rotateRL fail");
		}
	}
	//中序遍历
	void _inOrder(node* root)
	{
		if (root == nullptr)return;
		_inOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_inOrder(root->_right);
	}
	//后序删除
	void destruct(node* root)
	{
		if (root == nullptr)return;
		destruct(root->_left);
		destruct(root->_right);
		delete root;
	}
	//树的拷贝
	node* copy(node* root,node* parent)
	{
		if (root == nullptr) return nullptr;
		node* newnode = new node(root->_kv.first,root->_kv.second);
		newnode->_parent = parent;
		newnode->_left = copy(root->_left,newnode);
		newnode->_right = copy(root->_right,newnode);
		return newnode;
	}
};
//test.cpp
#include"AVLTree.h"
int main()
{
    AVLTree<int, string> t;
    t.insert(1, "a");
    t.insert(2, "b");
    t.insert(3, "c");
    t.insert(4, "d");
    t.insert(5, "e");
    t.insert(6, "f");

    cout << "中序遍历(必须有序):" << endl;
    t.inOrder();

    cout << "是否是AVL树:" << (t.isAVLTree() ? "是" : "否") << endl;
    cout << "树高度:" << t.height() << endl;
    cout << "节点个数:" << t.size() << endl;

    return 0;
}
相关推荐
yyuuuzz1 小时前
云服务器软件部署的几个常见问题
运维·服务器·开发语言·网络·云计算·php·apache
z落落1 小时前
Timer与DateTimePicker:控件使用全解析
开发语言·c#
Boom_Shu1 小时前
浅拷贝与深拷贝
开发语言·c++·算法
2601_961845151 小时前
2026法考资料pdf|电子版|资料已整理
开发语言·前端框架·pdf·c#·xhtml·csrf·view design
何以解忧,唯有..1 小时前
Go 语言数据类型详解:从基础到复合类型
开发语言·golang·mfc
Mortalbreeze1 小时前
C++ Lambda表达式详解:从捕获列表到底层原理
开发语言·c++
为何创造硅基生物2 小时前
LVGL
c++·ui
MATLAB代码顾问2 小时前
Python NumPy数值计算核心指南
开发语言·python·numpy
只做人间不老仙2 小时前
C++ grpc 拦截器示例学习
开发语言·c++·学习