AVL树的插入

本文深入介绍了AVL树的原理及实现方法,包括AVL树的结构、插入操作、旋转调整等核心内容,并提供了详细的代码实现。

为什么需要AVL树?

我们知道搜索二叉树通过对每一个结点限制规定:左子树都比比根节点小,右子树比根节点大,来达到O(logN)的搜索效率的:​​

但是一般的搜索二叉树还有一个很严重的问题:当左右子树高度差很大时,会导致搜索效率极度退化,甚至在顺序插入时退化至O(N):

很明显,普通的搜索二叉树在一些情况下并不能达到预期的O(logN)搜索效率,而其原因就是高度的极度不平衡。

而AVL树就通过旋转的方式来维持搜索二叉树的平衡,AVL树得名于它的发明者G. M. Adelson-Velsky和E. M. Landis。接下来,我们就对AVL树的插入进行详细介绍。

AVL树的结构

首先,AVL树是一棵严格平衡的搜索二叉树,也就是说,任何一个节点的左右子树高度差不超过1。这里我们在每一个节点中加入一个平衡因子来记录左右高度差,平衡因子为1代表该节点右子树比左子树高1,平衡因子0表示该节点左右子树高度相同,-1表示该节点左子树比右子树高1。

其次,由于AVL需要进行旋转调整,我们在节点中除了要保存左右孩子节点,还需要保存父亲节点,至于原因在插入时就能知道了。

template<class K,class V>
struct AVLTreeNode//AVL树的节点
{
	AVLTreeNode(const pair<K,V>& kv)
	{
		_father = nullptr;
		_left = nullptr;
		_right = nullptr;
		_bf = 0;
		_kv = kv;
	}
	AVLTreeNode<K, V>* _father;//父指针
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	pair<K, V> _kv;//节点保存的值为一个键值对pair
	int _bf;//平衡因子
};

template<class K,class V>
class AVLTree//AVL树
{
	typedef AVLTreeNode<K,V> Node;
public:
	AVLTree()//构造函数
	{
		_root = nullptr;
	}

    //...

private:
	Node* _root;
};

AVL树的插入

对于一颗空的AVL树,我们直接将新节点作为根节点_root即可:

	bool insert(const pair<K, V>& kv)
	{

		if (_root == nullptr)//如果AVL树为空
		{
			_root =new Node(kv);
			return true;
		}

    //...
    }

而在树不为空时,根据搜索二叉树的规则,如果待插入节点比当前节点小,那么待插入节点应该在当前节点的左子树,如果待插入节点比当前节点大,那么待插入节点应该在当前节点的右子树,如果待插入的节点与当前节点大小相同,我们不进行插入,而是直接返回false,这样的AVL树是可以起到去重的作用的。当然也可以插入相同大小的值,但是这种做法此处不进行展开。

依照上面的做法,我们可以从根节点向下比较,直到当前节点指向空,说明新插入节点应该插入到这个位置,或者值相同,返回false。

 但是,新节点应该插入到哪个位置我们虽然知道了,但此时当前节点已经是空指针了,而空指针并不能给我们任何信息,所以我们加入一个指针记录当前节点的父节点,这样我们就能知道新节点的父节点应该是谁,这样就能够让父节点的子结点指针指向新节点,从而将新节点插入到AVL树中。

代码实现:

		Node* father = nullptr;//father指针初始为空
		Node* cur = _root;//当前节点初始为根节点
		while (cur)//当当前节点不为空时进入循环进行搜索
		{
			if (cur->_kv.first < kv.first)//当当前节点小于新节点
			{
				father = cur;
				cur = cur->_right;//向当前节点的右子树进行搜索
			}
			else if (cur->_kv.first > kv.first)//当当前节点大于新节点
			{
				father = cur;
				cur = cur->_left;//向当前节点的左子树进行搜索
			}
			else
				return false;//相同时返回false,插入失败
		}

例如对下面这样一颗二叉树插入6,那么最终father指向5,cur为空

接着我们只需要构建一个新节点,然后根据新节点与father的大小来决定新节点应该插入到father节点的左孩子上还是右孩子上。 

		cur = new Node(kv);//把新节点new出来
		if (father->_kv.first > kv.first)//如果父节点大与新节点
		{
			father->_left = cur;//父亲的左孩子指向新节点
			cur->_father = father;//别忘了cur的父指针也要指向father
		}
		else
		{
			father->_right = cur;
			cur->_father = father;
		}

这样,一次搜索树式的插入就完成了,但是AVL树相比搜索二叉树,还需要对高度进行调节,所以接下来在插入使高度不平衡时我们还需要进行旋转处理。

AVL树的平衡因子调节

很明显,插入新节点会对其祖先的平衡因子产生影响,而这样的影响我们可以分为下面几类:

1.插入使得父节点的平衡因子变为0

 这种情况下,由于插入一个新节点,父节点的平衡因子只能增加1或者减少1,所以父节点原来的平衡因子必定是1或者-1。那么,这样的插入实际并没有改变以父节点为根的子树的高度。因为很明显,新插入的节点只是将这棵树矮的那一边填上了一个节点,并不会让整棵树的高度增加。就像上面的树的高度在插入3前后都是2一样。

所以,在这种情况下,这棵子树的高度没有改变,也就是说对于2的父节点来讲,这次插入并没有该变其子树的高度。

 所以2的父节点4的平衡因子不会变化,那么2的祖先的平衡因子就不用改变了,也就是说无需继续调整祖先的平衡因子了,而平衡因子也并没有变成-2或2,因此也无需进行旋转调整,插入到此结束。

2.插入使得父节点的平衡因子变为1或者-1

在这种情况下,父节点的平衡因子原来一定是0,也就是说父节点的左右子树高度是平衡的,而向这样的一颗树里插入一个节点,就会导致其高度增加。例如下图中以2为根节点的子树的高度在插入前为1,而插入后为2。所以以2为根的子树的高度增加了1,这就导致5的左子树高度增加1,影响了5的平衡因子。所以这种情况下调整完2的平衡因子之后,应该继续向上调整祖父节点的平衡因子。

 3.插入使得父节点平衡因子变为-2或2

 当平衡因子变为-2或者2时,说明左右子树高度差相差为2,高度不平衡,这种时候,就需要进行旋转来平衡高度了。例如下图插入5后,3的平衡因子变为1,继续向上调整,7的平衡因子由-1变为-2。

 

 AVL树的旋转

根据不同的情况,旋转可以分为四类:右单选,左单旋,左右双旋,右左双旋,下面我们先对右单旋进行介绍:

1.右单旋

当节点的平衡因子为-2时,说明是左子树过高导致的失衡,所以我们观察其左孩子的平衡因子,其左孩子的平衡因子为-1时,我们进行右单旋。

为了方便说明,我们使用下面这颗树进行后续的右单旋讲解:

 

 

 9的右子树高度为h,左子树在插入之前高度为h+1,而在6的左子树上插入之后,6的平衡因子更新为-1,9的平衡因子更新为-2,此时我们进行如下处理即可。

第一步:让9的左孩子指针指向6的右孩子节点

 

 

第二步:让6(9的左孩子)的右孩子指针指向9

 

 第三步:最后,别忘记旋转前9的父节点与9保持着连接父子关系,所以在旋转后,6应该替代9的位置与其父节点连接。

这样,6的平衡因子更新为0,9的平衡因子也为0,这棵树就满足了平衡的条件,并且由于高度与插入前没有变化,就不需要继续向上调整平衡因子了。

当然,上面的步骤对整个旋转过程进行了简化,实际上对于不同的情况,还需要更加细致的处理。这些细节在下面的代码实现中说明:

	void RotateR(Node* father)//传入要进行右单旋的树的根节点,即例子中的9
	{
		Node* cur = father->_left;//保存9的左孩子,即例子中的6
		Node* curRight = cur->_right;//保存6的右子树
		Node* FatherFather = father->_father;//保存9的父节点

		father->_left = curRight;//9的左指针指向6的右子树
		if (curRight)
			curRight->_father = father;//如果6的右子树存在的话,将其父指针指向9

		cur->_right = father;//9的左孩子的右指针指向9
		father->_father = cur;//9的父指针指向原来的左孩子

		if (father == _root)//9是否为根节点影响了第三步的调整
		{
			_root = cur;//如果9原来是根节点,将旋转后的6设为新的根节点
			_root->_father = nullptr;//将6的父指针置空
		}
		else
		{

            //如果9不是根节点,那么要将6与9原来的父节点进行连接
			if (FatherFather->_left == father)
			{
				FatherFather->_left = cur;
			}
			else
			{
				FatherFather->_right = cur;
			}
			cur->_father = FatherFather;
		}


		father->_bf = 0;//将9与6的平衡因子更新
		cur->_bf = 0;
	}

2.左单旋

当节点的平衡因子为2,其右孩子的平衡因子为1时,进行左单旋。

实际上左单旋类似于翻转的右单旋,下面仅对一颗具体的树的左单旋进行过程说明:

 对于上面的树,插入7后,9平衡因子更新为-1,继续向上更新,6平衡因子更新为1,继续向上更新,3平衡因子更新为2,所以要对3进行左单旋处理。

 第一步:让3的右指针指向6的左孩子

第二步:让6的左指针指向3

第三步:更新旋转前3的父节点与这棵树的连接关系

最终,树旋转成了下面这个样子:

最后别忘了更新平衡因子! 

 下面是左单旋的代码实现:

	void RotateL(Node* father)
	{
		Node* cur = father->_right;
		Node* curLeft = cur->_left;
		Node* FatherFather = father->_father;

		cur->_left = father;
		father->_father = cur;

		father->_right = curLeft;
		if (curLeft)
			curLeft->_father = father;

		if (father == _root)
		{
			_root = cur;
			_root->_father = nullptr;
		}
		else
		{
			if (FatherFather->_left == father)
			{
				FatherFather->_left = cur;
			}
			else
			{
				FatherFather->_right = cur;
			}
			cur->_father = FatherFather;
		}

		father->_bf = 0;
		cur->_bf = 0;
	}

3.左右双旋

当节点的平衡因子更新为-2,其左孩子平衡因子为1,我们需要进行左右双旋处理。

为什么这种情况下不可以使用右单旋呢?我们对下面这棵树进行右单旋处理试试:

 

 很明显,原来9的平衡因子是-2,旋转后6的平衡因子变成了2,这并没有起到平衡的作用。所以这种情况下我们需要进行双旋处理。

我们使用下面这棵树进行左右双旋的说明:

第一步:先对6进行左旋处理:

 第二步:再对9进行右旋处理:

 这样,旋转后的树以7作为根,且整颗树的高度与插入前没有变化(都是h+2),就不需要继续向上调整了。

 但是这里有个问题:平衡因子还需要继续调整吗?

在单旋过程,6,7,9的平衡因子都被置为0了,但是很明显,上面的双旋过程之后,9的平衡因子应该是-1,所以这里我们在双旋后,还要对平衡因子再进行调节。

但是无论新节点是插入到7的左子树还是7的右子树,都会导致左右双旋,而我们可以发现,实际上7的左子树最终成为了6的右子树,而7的右子树最终成为了9的左子树,而这也决定了到底是6的平衡因子需要调节,还是9的平衡因子需要调节。

例如上面的例子,我们在7的左子树进行插入,那么最终6的平衡因子是0,9的平衡因子是-1,所以我们需要调整9的平衡因子。所以在代码实现中,我们可以记录插入后7的平衡因子,这样就知道新节点到底插入在了7的左子树还是右子树上,以便决定最终我们要调节哪个节点的平衡因子。

下面就是双旋的代码实现:

int bf = cur->_right->_bf;//记录7的平衡因子

//左右双旋
RotateL(cur);
RotateR(father);

//根据记录好的bf来调节平衡因子
if (bf == 1)
	cur->_bf = -1;
else if (bf == -1)
	father->_bf = 1;

4.左右双旋

当节点的平衡因子更新为2,其左孩子平衡因子为-1,我们需要进行左右双旋处理。

右左双旋的调整与左右双旋呈镜像,这里不再赘述,仅给出代码参考:

int bf = cur->_left->_bf;
RotateR(cur);
RotateL(father);
if (bf == -1)
	cur->_bf = 1;
else if (bf == 1)
	father->_bf = -1;

3.平衡测试与代码总览

1.平衡测试

相信经过上面的学习,同学们已经掌握了AVL树的插入了,但是即使代码能够跑通,也只能说明这是一颗二叉搜索树,而树实际是否平衡我们并不知道,可以通过下面的测试函数进行测试:

	void testbf()//测试平衡因子
	{
		_testbf(_root);
	}
	int _testbf(Node* cur)
	{
		if (cur == nullptr)
			return 0;

		int left = _testbf(cur->_left);//得到左子树高度
		int right = _testbf(cur->_right);//得到右子树高度

		if (right - left != cur->_bf)//判断左右高度差与平衡因子是否相等
			cout << "truth of high sub : " << right - left; 
            cout << "  bf : " << cur->_bf << endl;

		return max(left, right) + 1;//返回自己的高度
	}

其思想是通过递归计算每一个节点左右子树的高度差,再与平衡因子进行对比,以此来判断我们所写的AVL树实际上是否平衡了。 

2.代码总览

template<class K,class V>
struct AVLTreeNode//节点结构
{
	AVLTreeNode(const pair<K,V>& kv)
	{
		_father = nullptr;
		_left = nullptr;
		_right = nullptr;
		_bf = 0;
		_kv = kv;
	}
	AVLTreeNode<K, V>* _father;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	pair<K, V> _kv;
	int _bf;//右高度-左高度
};

template<class K,class V>
class AVLTree
{
	typedef AVLTreeNode<K,V> Node;

public:
	AVLTree()
	{
		_root = nullptr;
	}

	bool insert(const pair<K, V>& kv)
	{

		if (_root == nullptr)
		{
			_root =new Node(kv);
			return true;
		}

		Node* father = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < kv.first)
			{
				father = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				father = cur;
				cur = cur->_left;
			}
			else
				return false;
		}

		cur = new Node(kv);
		if (father->_kv.first > kv.first)
		{
			father->_left = cur;
			cur->_father = father;
		}
		else
		{
			father->_right = cur;
			cur->_father = father;
		}
		
		while (father)
		{
			if (cur == father->_left)
			{
				father->_bf--;
			}
			else
			{
				father->_bf++;
			}

			if (father->_bf == 0)
			{
				break;
			}
			else if (father->_bf == -1 || father->_bf == 1)
			{
				cur = father;
				father = father->_father;
			}
			else if (father->_bf == 2 || father->_bf == -2)
			{
				if (father->_bf == 2 && cur->_bf == 1)//左单旋
				{
					RotateL(father);
				}
				else if (father->_bf == 2 && cur->_bf == -1)
				{
					int bf = cur->_left->_bf;
					RotateR(cur);
					RotateL(father);
					if (bf == -1)
						cur->_bf = 1;
					else if (bf == 1)
						father->_bf = -1;
				}
				else if (father->_bf == -2 && cur->_bf == -1)//右单旋
				{
					RotateR(father);
				}
				else if (father->_bf == -2 && cur->_bf == 1)
				{
					int bf = cur->_right->_bf;
					RotateL(cur);
					RotateR(father);
					if (bf == 1)
						cur->_bf = -1;
					else if (bf == -1)
						father->_bf = 1;
				}
				else
				{
					assert(false);
				}
				break;
			}
		}
		return true;
	}


	void RotateR(Node* father)
	{
		Node* cur = father->_left;
		Node* curRight = cur->_right;
		Node* FatherFather = father->_father;

		father->_left = curRight;
		if (curRight)
			curRight->_father = father;

		cur->_right = father;
		father->_father = cur;

		if (father == _root)
		{
			_root = cur;
			_root->_father = nullptr;
		}
		else
		{
			if (FatherFather->_left == father)
			{
				FatherFather->_left = cur;
			}
			else
			{
				FatherFather->_right = cur;
			}
			cur->_father = FatherFather;
		}

		father->_bf = 0;
		cur->_bf = 0;
	}

	void RotateL(Node* father)
	{
		Node* cur = father->_right;
		Node* curLeft = cur->_left;
		Node* FatherFather = father->_father;

		cur->_left = father;
		father->_father = cur;

		father->_right = curLeft;
		if (curLeft)
			curLeft->_father = father;

		if (father == _root)
		{
			_root = cur;
			_root->_father = nullptr;
		}
		else
		{
			if (FatherFather->_left == father)
			{
				FatherFather->_left = cur;
			}
			else
			{
				FatherFather->_right = cur;
			}
			cur->_father = FatherFather;
		}

		father->_bf = 0;
		cur->_bf = 0;
	}
	
	
private:
	Node* _root;
};

AVL得名于它的发明者G. M. Adelson - Velsky和E. M. Landis,又叫高度平衡。其插入操作的实现原理和方法如下: ### 实现原理 AVL在进行插入操作后,为保证每个结点的左右子高度之差的绝对值不超过1,以达到高度平衡的目的,需要对进行一次或多次旋转。与搜索二叉不同,在插入过程中要更新每个节点的平衡因子,为后续不平衡时的旋转操作做准备[^1][^2]。 ### 实现方法 以下是实现AVL插入操作的大致步骤和示例代码: ```python # 定义AVL的节点类 class TreeNode: def __init__(self, key): self.key = key self.left = None self.right = None self.height = 1 # 定义AVL类 class AVLTree: def insert(self, root, key): # 执行标准的BST插入 if not root: return TreeNode(key) elif key < root.key: root.left = self.insert(root.left, key) else: root.right = self.insert(root.right, key) # 更新当前节点的高度 root.height = 1 + max(self.get_height(root.left), self.get_height(root.right)) # 获取平衡因子 balance = self.get_balance(root) # 左左情况 if balance > 1 and key < root.left.key: return self.right_rotate(root) # 右右情况 if balance < -1 and key > root.right.key: return self.left_rotate(root) # 左右情况 if balance > 1 and key > root.left.key: root.left = self.left_rotate(root.left) return self.right_rotate(root) # 右左情况 if balance < -1 and key < root.right.key: root.right = self.right_rotate(root.right) return self.left_rotate(root) return root def left_rotate(self, z): y = z.right T2 = y.left # 执行旋转 y.left = z z.right = T2 # 更新高度 z.height = 1 + max(self.get_height(z.left), self.get_height(z.right)) y.height = 1 + max(self.get_height(y.left), self.get_height(y.right)) return y def right_rotate(self, z): y = z.left T3 = y.right # 执行旋转 y.right = z z.left = T3 # 更新高度 z.height = 1 + max(self.get_height(z.left), self.get_height(z.right)) y.height = 1 + max(self.get_height(y.left), self.get_height(y.right)) return y def get_height(self, root): if not root: return 0 return root.height def get_balance(self, root): if not root: return 0 return self.get_height(root.left) - self.get_height(root.right) ``` 以上代码实现了AVL插入操作,包括标准的BST插入、高度更新、平衡因子计算和旋转操作。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值