手撕AVL树谁看谁撕。看了之后你到时候撕给面试官看。

目录

1.AVL树的概念

2.一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

3.AVL树的节点

4.AvL树的插入

1. 按照二叉搜索树的方式插入新节点

2. 调整节点的平衡因子

2.1.右单旋

2.2.左单旋

2.3. 左右双旋

2.4.右左双旋

5.AVL树检测:

6.AVL树的性能

7.最后附上AVL树的全部代码:

因为马上期末考试了/(ㄒoㄒ)/,所以只写了AVL树的插入详细解法,但质量绝对保证包看包会,等期末之后更新AVL树的删除操作!!!

1.AVL树的概念

  • 二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行整),即可降低树的高度,从而减少平均搜索长度。
  • 二叉搜索树的最优情况和最差情况

    手撕AVL树谁看谁撕。看了之后你到时候撕给面试官看。_第1张图片

2.一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

  • 1.它的左右子树都是AVL树
  • 2.左右子树高度之差(简称平衡因子)的绝对值不超过1(-1或0或1),这里为什么高度差不是0呢?这是因为假如二叉树的节点个数是偶数个就一定会不满足这个条件。所以我们退而求其次将AVL树的平衡因子要求为绝对值不超过1即可。
  • 如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在O(log(n))搜索时复杂度O(log(n))。

3.AVL树的节点

  • 我们这里将AVL树的节点给出的是孩子双亲表示法,然后在每个节点中加入平衡因子,而平衡因子我们约定为(右子树的高度-左子树的高度)

    手撕AVL树谁看谁撕。看了之后你到时候撕给面试官看。_第2张图片

4.AvL树的插入

  • AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:
  • 1. 按照二叉搜索树的方式插入新节点

    手撕AVL树谁看谁撕。看了之后你到时候撕给面试官看。_第3张图片

  • 2. 调整节点的平衡因子

  • 因为插入了节点那么自插入节点之上的节点的平衡因子是一定要改变的那么我们就可以分为多种情况

    手撕AVL树谁看谁撕。看了之后你到时候撕给面试官看。_第4张图片

  • 我们来看上面代码最后一种情况:当插入节点之后导致当前节点的平衡因子失衡之后的调整:在调整之前首先我们来明白一些本文中调整二叉搜索树的概念就是各个节点的命名含义:

    手撕AVL树谁看谁撕。看了之后你到时候撕给面试官看。_第5张图片

  • 知道了上面我们对于要调整树的名字命名接下来我们直接上硬菜(接下来将介绍调整平衡因子绝对值为2的四种方法“右单旋”“左单旋”“右左单旋”“左右单旋”)
  • 注意:接下来的图中出现的虚线节点表明当前节点有可能存在有可能不存在。
  • 2.1.右单旋

  • 我们先来看第一种情况:就是插入失衡树的较高左子树的左侧(也就是当前失衡树的平衡因子为-2)

    手撕AVL树谁看谁撕。看了之后你到时候撕给面试官看。_第6张图片

  • 这里parent为跟根节点的子树已经失衡那么当前我们要进行右旋操作

    手撕AVL树谁看谁撕。看了之后你到时候撕给面试官看。_第7张图片

  • 接下来我们来演示右旋的过程:
  • 第一步:将parent的左节点连到subL的右节点:这里subLR的父节点就被改变了,那么我们这里就要更新subLR的父节点,这里右节点有可能有也有可能没有那么我们在代码实现的时候一定要判断subLR是否存在然后再更新它的父节点。

    手撕AVL树谁看谁撕。看了之后你到时候撕给面试官看。_第8张图片

  • 第二步:我们将parent旋转下来,也就是将subL的右节点连接为parent,那么此时我们就需要将parent的父节点更新为subL,此时因为我们将parent旋转下去那么我们就要更新subL的父节点,那么要判断将parent连在pparent的左还是右。到这里我们就将旋转完成了。

    手撕AVL树谁看谁撕。看了之后你到时候撕给面试官看。_第9张图片

  • 那么我们来总结一下在旋转中要注意的几个地方

    手撕AVL树谁看谁撕。看了之后你到时候撕给面试官看。_第10张图片

  • 接下来我们就要更新平衡因子了,我们这里更新平衡因子的时候也分为四种情况我们来一一分析:

    手撕AVL树谁看谁撕。看了之后你到时候撕给面试官看。_第11张图片

  • 更新平衡因子的四种情况:

    手撕AVL树谁看谁撕。看了之后你到时候撕给面试官看。_第12张图片

    手撕AVL树谁看谁撕。看了之后你到时候撕给面试官看。_第13张图片

    手撕AVL树谁看谁撕。看了之后你到时候撕给面试官看。_第14张图片

    手撕AVL树谁看谁撕。看了之后你到时候撕给面试官看。_第15张图片

  • 从上面四种情况我们看出右单旋只要parent和subL的平衡因子都更新为0即可。其他的无需变动
  • 我们给出右单旋的代码:

    手撕AVL树谁看谁撕。看了之后你到时候撕给面试官看。_第16张图片

  • 2.2.左单旋

  • 新节点插入当前失衡树较高右子树的右侧---右右:左单旋(也就是当前失衡树的平衡因子为-2)

    手撕AVL树谁看谁撕。看了之后你到时候撕给面试官看。_第17张图片

  • 这里左单旋的做法和规律其实是和右单旋的规律是一样的,大家可以仿照我上面右单旋的例子来画图理解。其更新节点的规律也是和上面右单旋的规律一样。这里就直接给出代码:

    手撕AVL树谁看谁撕。看了之后你到时候撕给面试官看。_第18张图片

  • 其实上面无论是使用左单旋的方法,还是使用右单旋的方法调整的都是插入节点插入在较高子树的外侧的情况,不信可以去上面仔细观察,那么我们接下来探讨将节点插入较高子树的内测的插入:
  • 2.3. 左右双旋

  • 新节点插入较高左子树的右侧---左右:先左单旋再右单旋

    手撕AVL树谁看谁撕。看了之后你到时候撕给面试官看。_第19张图片

  • 我们看左右双旋的时候可以看到就是先将我们较高子树的根节点作为parent其内测节点作为subR先左单旋,然后将parent和其subL进行右单旋转。重点是要看其旋转之后的平衡因子的更新:我们大致可以分为以下三种情况:

    手撕AVL树谁看谁撕。看了之后你到时候撕给面试官看。_第20张图片

    手撕AVL树谁看谁撕。看了之后你到时候撕给面试官看。_第21张图片

    手撕AVL树谁看谁撕。看了之后你到时候撕给面试官看。_第22张图片

  • 我们可以看到当前三种情况双旋完成之后节点平衡因子的更新和subL的R的平衡因子的值密切相关:所以我们可以在代码当中体现出来

    手撕AVL树谁看谁撕。看了之后你到时候撕给面试官看。_第23张图片

  • 2.4.右左双旋

  • 新节点插入较高右子树的左侧---右左:先右单旋再左单旋

    手撕AVL树谁看谁撕。看了之后你到时候撕给面试官看。_第24张图片

  • 这里具体流程和更新平衡因子和上面的左右双旋是类似的所以这里我们直接给出代码

    手撕AVL树谁看谁撕。看了之后你到时候撕给面试官看。_第25张图片

5.AVL树检测:

  • 我们可以通过检测AVL树的右子树-左子树的差值是否等于当前子树的的平衡因子来检测我们当前构造的AVL树的平衡因子是否正确,同样也要判断我们当前AVL树的平衡因子绝对值的大小是否满足不超过1的要求。

    手撕AVL树谁看谁撕。看了之后你到时候撕给面试官看。_第26张图片

  • 6.AVL树的性能

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

#include
#include
using namespace std;
template
struct AVL_NODE
{
	AVL_NODE(const T &value=T())
	:_parent(nullptr),_left(nullptr),_right(nullptr),val(value),bf(0)
	{}
	AVL_NODE* _parent;
	AVL_NODE* _left;
	AVL_NODE* _right;
	T val;
	int bf;//平衡因子
};
template
class AVL_Tree
{
	typedef AVL_NODE Node;
public:
	AVL_Tree()
	:_root(nullptr)	
	{}
	~AVL_Tree()
	{}
	bool Insert(const T& value)
	{
		if (_root == nullptr)
		{
			_root = new Node(value);
			return true;
		}
		else
		{
			Node* parent = nullptr;
			Node* cur = _root;
			while (cur!=nullptr)
			{
				parent = cur;
				if (cur->val > value)
				{
					cur = cur->_left;
				}
				else if(cur->val_right;
				}
				else
				{//不可以插入相同的val的节点
					return false;
				}
			}
			cur = new Node(value);
			if (parent->val < value)
			{
				parent->_right =cur ;
			}
			else
			{
				parent->_left = 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 = cur->_parent;
				}
				//最后一种情况就是更新之后平衡因子为-2或者+2失衡了那么我们就需要对当前的节点进行旋转操作,然后调整更新
				//经过调整后的节点的平衡因子
				else
				{
					//更新平衡因子
					if (parent->bf == -2)
					{//这里如果parent的平衡因子为-2那么节点一定插在当前树的较高左子树
						
						if (cur->bf == -1)
						{//cur==-1,那么节点就是插在了cur的外侧那么我们要右旋
							RotateR(parent);
						}
						else if (cur->bf == 1)
						{//cur==1,节点插入在cur的内侧那么我们要左右双旋
							RotateLR(parent);
						}
						
					}
					if (parent->bf == 2)
					{//这里如果parent的平衡因子我们2那么节点一定插入在当前树的较高右子树
						if (cur->bf == 1)
						{//cur->bf==1,那么节点插入到了cur的外侧也就是右侧我们要左单旋转
							RotateL(parent);
						}
						else if (cur->bf == -1)
						{//cur->bf==-1,那么节点插入到了cur的内测也就是左侧我们需要右左双旋
							RotateRL(parent);
						}
					}
					break;
				}
			}
		}
	}
//中序遍历:
	void InOrder()
	{
		cout << "中序遍历结果:";
		mid(_root);
		cout << endl;
	}
//判断是否是AVL树
	bool IsValidAVLTree()
	{
		return _IsValidAVLTree(_root);
	}
private:
//获取树的高度
	int GetHeight(Node* root)
	{
		if (nullptr == root)
			return 0;

		int leftHeight = GetHeight(root->_left);
		int rightHeight = GetHeight(root->_right);
		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}
//判断是否是AVL树
	bool _IsValidAVLTree(Node* root)
	{
		if (nullptr == root)
			return true;

		// 根节点
		int leftHegiht = GetHeight(root->_left);
		int rigthHegiht = GetHeight(root->_right);
		if (rigthHegiht - leftHegiht != root->bf || abs(root->bf) > 1)
		{
			cout << "Node:" << root->val << endl;
			cout << rigthHegiht - leftHegiht << " " << root->bf << endl;
			return false;
		}


		// 根的左子树 和 根的右子树组成
		return _IsValidAVLTree(root->_left) &&
			_IsValidAVLTree(root->_right);
	}
//中序遍历
	void mid(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		mid(root->_left);
		cout << root->val <<" ";
		mid(root->_right);
	}
	//右单旋:用于插入较高左子树的左边节点
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;//将当前失衡节点的较高左子树保存
		Node* subLR = subL->_right;//将较高左子树的右节点保存
		Node* pparent = parent->_parent;//将当前失衡节点的parent保存
		parent->_left = subLR;//将parent的左连到当前左子树的右节点
		//如果subLR存在那么将其父节点更新
		if (subLR)
		{
			subLR->_parent = parent;
		}
		//将parent右旋下来:
		subL->_right = parent;
		//parent旋下来就要更新parent的父节点
		parent->_parent = subL;
		//此时subL就要更新父节点			
		subL->_parent = pparent;
		if (pparent)
		{
			if (parent == pparent->_right)
			{
				pparent->_right = subL;
			}
			else
			{
				pparent->_left = subL;
			}
		}
		else
		{//如果pparent不存在那么就将根节点更新为subL
			_root = subL;
		}		
		//更新节点的平衡因子一共分为四种情况:
		//1.当前节点有右孩子,父亲节点有右孩子:
		//2.当前节点有右孩子,父亲节点没有右孩子:不可能有这种情况
		//3.当前节点没有右孩子,父亲节点有右孩子,是没有这种情况的,这种情况就和第四种情况类似因为如果当前节点没有右孩子,
		//然后在插入一个左孩子那么当前的节点本身就不平衡了就需要调整
		//4.当前节点没有右孩子,父亲节点没有右孩子
		//第一种和第四种情况一样都是直接将subL和parent的平衡因子更新为0
		subL->bf = parent->bf = 0;
	}


	//左单旋:用于节点插入较高右子树的右边:
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;//将当前失衡节点的较高右子树保存
		Node* subRL = subR->_left;//将较高右子树的左节点保存
		Node* pparent = parent->_parent;//将当前失衡节点的parent保存
		parent->_right = subRL;//将parent的右连到当前右子树的左节点
		//如果subRL存在那么将其父节点更新
		if (subRL)
		{
			subRL->_parent = parent;
		}
		//将parent右旋下来:
		subR->_left = parent;
		//parent旋下来就要更新parent的父节点
		parent->_parent = subR;
		//此时subR就要更新父节点			
		subR->_parent = pparent;
		if (pparent)
		{
			if (parent == pparent->_right)
			{
				pparent->_right = subR;
			}
			else
			{
				pparent->_left = subR;
			}
		}
		else
		{//如果pparent不存在那么就将根节点更新为subL
			_root = subR;
		}
		//更新各节点的平衡因子
		subR->bf = parent->bf = 0;
	}

	void RotateLR(Node* parent) {
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		// 旋转之前,保存subLR的平衡因子,旋转完成之后,需要根据该平衡因子来调整其他节点的平衡因子
			int _bf = subLR->bf;

		// subL进行左单旋
		RotateL(parent->_left);

		// 再对parent进行右单旋
		RotateR(parent);
		if (1 == _bf)
			subL->bf = -1;
		else if (-1 == _bf)
			parent->bf = 1;
	}

	//新节点插入较高右子树的左侧(内测)--- 右左:先右单旋再左单旋
	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		//保存subRL的平衡因子,旋转完成之后根据该平衡因子来调整其他节点的平衡因子
		int bf = subRL->bf;
		RotateR(parent->_right);
		RotateL(parent);
		if(bf==1)
		{
			parent->bf = -1;
		}
		else if(-1==bf)
		{
			subR->bf = 1;
		}
	}
	Node* _root;
};

int main()
{


	AVL_Tree t;
	//vector v = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	vector v = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	for (int a:v)
	{
		t.Insert(a);
	}
	t.InOrder();
	if (t.IsValidAVLTree())
		cout << "t is valid AVLTree" << endl;
	else
		cout << "t is invalid AVLTree" << endl;
	return 0;
}

 好啦看到这里相信AVL树的插入已经是小case了,等期末考试之后必更AVL树的删除点个赞支持一下吧。手撕AVL树谁看谁撕。看了之后你到时候撕给面试官看。_第27张图片

你可能感兴趣的:(函数,b树,数据结构)