AVL树-平衡二叉树

Posted 小羊教你来编程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AVL树-平衡二叉树相关的知识,希望对你有一定的参考价值。

在这里插入图片描述

目录:

一.概念&原理

在之前我们理解到了二叉搜索树,可是我们发现如果数据有序或者接近有序的时候,二叉搜索树会退化成单支树
在这里插入图片描述

AVL树就是平衡二叉树,只不过在二叉搜索树的基础上加了一个平衡因子,就构成了现在的平衡二叉树. 二叉搜索树链接
^
这里我们注重理解平衡因子

二.平衡因子

平衡因子: 平衡因子是判断对应的二叉树是否平衡的一个整型值,来稳定二叉树,防止二叉树退化成上面的单支树.

规则:

1.在每一个对应的节点上都存在一个平衡因子,来表示二叉树是否平衡
~
2.每个节点的平衡因子=右子树的层数-左子树的层数
~
3.当对应的平衡因子处于(-1~1)的时候,为平衡二叉树,否则就需要调整这个二叉树

在这里插入图片描述

三.定义

在AVL树的内部存在: 左孩子节点.右孩子节点.父节点.对应存放的数据.还有平衡因子,我们创建一个结构体,封装起来就可以作为创建一个节点内部应该存储的东西.

template<class T>		//泛型声明
struct AVLNode{		//创建的结构体

	AVLNode<T>* _parent;	//创建对应的父节点
	AVLNode<T>* _left;		//左孩子
	AVLNode<T>* _right;		//右孩子
	T _val;		//内部对应的值
	//平衡因子
	int _bf;	

	AVLNode(const T& val = T())		//初始化一个对应的节点
		:_parent(nullptr)		
		, _left(nullptr)
		, _right(nullptr)		//节点赋予nullptr
		, _val(val)		//数据直接传值
		, _bf(0)		//平衡因子直接化为0
	{}
};

这样就将创建一个对应的节点所需要的东西全部传入了.,在后面使用这个结构体的时候,我们只用将其起一个别名,然后进行new的方式创建使用就可以了.

四.AVL树的旋转

1.左旋接口理解

对于左旋转接口的理解:因为在AVL树进行插入后,节点的平衡因子会发生变化,由于这种原因所以我们需要对这个插入新的节点的二叉树进行左旋的操作,以至于让这个二叉树恢复到平衡二叉树.

在这里插入图片描述

	void RotateL(Node* parent){

		Node* subR = parent->_right;	//这里定义的是父节点对应的右子树,也就是B
		Node* subRL = subR->_left;		//这里取到的是B节点的左子树,也就是左旋需要转换的节点

		subR->_left = parent;			//让B和对应的父节点相连
		parent->_right = subRL;			//将父节点的右子树和B相连
		if (subRL)		//当这个中间状态的节点存在的时候女
			subRL->_parent = parent;	//则让其子节点的父节点指向真正的父节点
		if (parent == _root){			//当父节点为根节点的时候

			_root = subR;	//指向父节点
			subR->_parent = nullptr;	//其内部的指向转换为null
		}
		else{	//如果都存在的时候

			Node* pparent = parent->_parent;	//定义父亲的父亲节点
			if (pparent->_left == parent)		//如果祖父节点的左边是父亲节点则将其指向
				pparent->_left = subR;
			else
				pparent->_right = subR;			//反之则为右边的指向
			subR->_parent = pparent;		//最终让将其左旋产生的父节点指向祖父的节点中
		}

		parent->_parent = subR;	//让父亲的父亲节点指向旋转的子节点
		parent->_bf = subR->_bf = 0;		//因为在发生左旋后,其内部的平衡因子均变为0
	}

对于左旋的解释已经比较清晰了,主要的就是画图一步一步的来理解这个过程.

2.右旋接口理解

在这里插入图片描述

对于右旋的操作和左旋的操作是类似的,我们可以类比的来理解,我不过多的注释了.

	void RotateR(Node* parent){

		Node* subL = parent->_left;		//同样创建其对应的节点指向
		Node* subLR = subL->_right;		//其内部的节点指向

		subL->_right = parent;
		parent->_left = subLR;

		if (subLR)
			subLR->_parent = parent;
		//判断parent是否是根节点
		if (parent == _root){

			//根节点
			_root = subL;
			subL->_parent = nullptr;
		}
		else{

			Node* pparent = parent->_parent;
			if (pparent->_left == parent)
				pparent->_left = subL;
			else
				pparent->_right = subL;
			subL->_parent = pparent;
		}
		parent->_parent = subL;

		subL->_bf = parent->_bf = 0;
	}

3.insert接口理解

这里的关于Insert的代码是相连的,我只不过是进行了拆分,方便更容易的理解.

对于插入一个元素的实现,我们也是首先遍历这个二叉树,找到对应的添加位置,将对应的数据进行插入实现,还需要根据插入的位置利用左旋和右旋操作来实现平衡二叉树.

	bool insert(const T& val){		//插入函数

		//二叉搜索树
		if (_root == nullptr){

			_root = new Node(val);	//如果根节点为空则直接创建为根节点
			return true;
		}

		Node* cur = _root;		//反之则创建对应的根节点
		Node* parent = nullptr;		//父节点为null
		while (cur){	//存在对应的插入位置

			parent = cur;		//将数据进行插入
			if (cur->_val == val)
				return false;
			else if (cur->_val > val)
				cur = cur->_left;
			else
				cur = cur->_right;
		}

		cur = new Node(val);		//利用Node创建新的节点
		if (parent->_val > val)
			parent->_left = cur;
		else
			parent->_right = cur;

		cur->_parent = parent;

		//调整,从parent开始
		while (parent){

			//更新parent的平衡因子
			if (parent->_left == cur)
				--parent->_bf;
			else
				++parent->_bf;

			if (parent->_bf == 0)	//parent短的子树高度+1
				break;
			else if (parent->_bf == 1 || parent->_bf == -1){

				//继续向上更新
				cur = parent;
				parent = parent->_parent;
			}
			else if (abs(parent->_bf) == 2){

上面是对于插入节点的理解,下面是对于插入元素后的二叉树来进行分情况的调整

(1)新节点插入到较高左子树的左边-左左:右单旋

我们这里的区分是通过父节点和其对应子节点的平衡因子的大小来实现的 !!!
在这里插入图片描述

if (parent->_bf == -2 && cur->_bf == -1){

					//左边的左边高,则右旋
					RotateR(parent);
				}

(2)新节点插入到较高右子树的右边-右右:左单旋

在这里插入图片描述

				else if (parent->_bf == 2 && cur->_bf == 1){

					RotateL(parent);	//父节点左旋
				}

(3)新节点插入到较高左子树的右边-左右:先左单旋再右单旋

在这里插入图片描述

				else if (parent->_bf == -2 && cur->_bf == 1){

					Node* subLR = cur->_right;
					int bf = subLR->_bf;

					RotateL(cur); 	//先子节点左旋
					RotateR(parent);	//再父节点右旋

					if (bf == 1){	因为发生更新的时候,会有一个节点更新发生误差,所以手动更新

						parent->_bf = 0;
						cur->_bf = -1;
					}
					else if (bf == -1){

						parent->_bf = 1;
						cur->_bf = 0;
					}
				}

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

在这里插入图片描述

				else if (parent->_bf == 2 && cur->_bf == -1){

					//先将subRL对应的bf获取
					Node* subRL = cur->_left;
					int bf = subRL->_bf;

					RotateR(cur);
					RotateL(parent);

					//在发生右左双旋后,对应的子节点的bf值可能会不同,需要更新
					//这里运用判断的方式来区分所挂的节点在那一个位置,然后再进行对应bf的更新
					if (bf == 1){

						cur->_bf = 0;  因为发生更新的时候,会有一个节点更新发生误差,所以手动更新
						parent->_bf = -1;
					}
					else if (bf == -1){

					cur->_bf = 1;
					parent->_bf = 0;
					}
				}
				break;
			}
		}
		return true;
	}

五.打印接口

这里的打印接口比较简单,主要运用递归的方式进行循环去内部的val即可

	void inorder(){

		_inorder(_root);	//调用下面的函数
		cout << endl;
	}

	void _inorder(Node* root){

		if (root){

			_inorder(root->_left);		//先遍历左节点
			cout << root->_val << " ";
			_inorder(root->_right);		//在遍历对应的右节点
		}
	}

六.验证AVL树接口

对于AVL树的验证就是通过平衡因子和看左右子树的高度差来判断是否是平衡二叉树.

	int Height(Node* root){		//封装函数来实现求对应子节点的高度判断

		if (root == nullptr)
			return 0;

		int left = Height(root->_left);
		int right = Height(root->_right);
		return left > right ? left + 1 : right + 1;
	}

	bool _isBalance(Node* root){	//判断是否平衡的函数

		if (root == nullptr)
			return true;

		//查看平衡因子是否和左右子树高度差一致
		//调用函数来查看对应子树的高度
		int left = Height(root->_left);
		int right = Height(root->_right);
		if (right - left != root->_bf){

			cout << " Node: " << root->_val << " bf:"
				<< root->_bf << " height gap: " << right - 1;		//输出
			return false;
		}

		return abs(root->_bf) < 2
			&& _isBalance(root->_left)		//循环判断
			&& _isBalance(root->_right);
			//平衡则输出1
	}

	bool isBalance(){	//最终封装的接口.

		return _isBalance(_root);
	}

总体还是理解平衡二叉树插入的实现,因为在插入的时候,会涉及到遍历和左旋右旋的操作,比较难理解,注重对于左旋右旋的理解,以及左右双旋和右左双旋的理解.

以上是关于AVL树-平衡二叉树的主要内容,如果未能解决你的问题,请参考以下文章

数据结构~基础2~树《二叉树二叉搜索树AVL树B树红黑树》的设计~高度平衡二叉树AVL树

算法平衡二叉树 Avl 树

树结构实际应用之平衡二叉树(AVL 树)

其他类型的树1:平衡二叉树和AVL树

C++ 实现平衡二叉树(AVL树)(完整代码)

AVL平衡二叉树