[High-order data structure] AVL tree (implemented in C++)

⭐Blog homepage: ️CS semi homepage
⭐Welcome to follow: like, favorite and leave a message
⭐ Column series: Advanced C++
⭐Code repository: Advanced C++
Home It is not easy for people to update. Your likes and attention are very important to me. Friends, please like and follow me. Your support is the biggest motivation for my creation. Friends are welcome to send private messages to ask questions. Family members, don’t forgetLike and collect + follow! ! !


1. Concept

Although the binary search tree can shorten the search efficiency, if the data is ordered or close to ordered, the binary search tree will degenerate into a single branch tree. Searching for elements is equivalent to searching for elements in a sequence table, which is inefficient.

Insert image description here

Therefore, two Russian mathematicians, G.M. Adelson-Velskii and E.M. Landis, invented a method to solve the above problem in 1962: after inserting a new node into the binary search tree, if the left and right of each node can be guaranteed If the absolute value of the difference in subtree heights does not exceed 1 (nodes in the tree need to be adjusted), the height of the tree can be reduced, thereby reducing the average search length.

We use the concept of AVL tree:
The AVL tree can be an empty tree or a binary search tree with the following properties:
1. Its left and right subtrees are both AVL trees and satisfy the properties of AVL trees
2. The height difference (balance factor) of the left and right subtrees does not exceed 1 (-1/0 /1)
3. Balance factor = height of right subtree - height of left subtree (artificially specified)

Insert image description here

If a binary search tree is highly balanced, it is an AVL tree. If it has n nodes, its height can be kept at O(logN), and the search time complexity is also O(logN).

Because the AVL tree controls the balance factor, this makes our AVL tree search very convenient, especially when the amount of data is large, the time complexity is O(logN), and 1 billion data only need to be searched 30 times. Efficiency has been greatly improved.

Insert image description here

2. Definition of AVL tree nodes

We implement the AVL tree of the K-V model here. For the convenience of subsequent operations, we define the AVL tree as a three-way chain, that is, a node links the left and right subtrees, and the child node of this node is linked to the parent node. Its parent node, this is the structure of the trident chain we defined. In addition, we need to introduce a balance factor in each node, that is, the size of the right subtree minus the left subtree, and this balance factor can be 0 at the beginning. , defined as follows:

//Key-Value模型
template<class K, class V>
struct AVLTreeNode
{
    
    
	//构造函数
	AVLTreeNode(const pair<K,V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _bf(0)
	{
    
    }

	// 定义三叉链
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;

	//存储键位值
	pair<K, V> _kv;

	//平衡因子
	int _bf;
};

3. Insertion of AVL tree (difficult)

There are four secrets for inserting AVL trees, which are:
1. Find (first find the position that needs to be inserted)
2. Insert ( Insertion operation)
3. Update (update balance factor)
4. Rotation (left/right rotation according to the unsatisfied condition of the balance factor)

1. Find

The method of finding is very simple. It is the same as the search binary tree we wrote before. We write the template directly below:

template<class K,class V>
class AVLTree
{
    
    
	typedef AVLTreeNode<K, V> Node;
public:
	// 插入
	bool Insert(const pair<K, V>& kv)
	{
    
    
		// 找
		// 1、待插入结点key比当前结点小就往左子树跑
		// 2、待插入结点key比当前结点大就往右子树跑
		// 3、待插入结点key和当前结点的值相等就显示插入失败
		// 刚开始进去为空树
		if (_root == nullptr)
		{
    
    
			_root = new Node(kv);
			return true;
		}

		Node* cur = _root;
		Node* parent = nullptr;
		// 不是空树
		while (cur)
		{
    
    
			if (cur->_kv.first < kv.first)
			{
    
    
				parent = cur; // 保存一下cur原本的结点
				cur = cur->_right; // 往左跑
			}
			else if (cur->_kv.first > kv.first)
			{
    
    
				parent = cur; // 保存一下cur原本的结点
				cur = cur->_left;
			}
			else
			{
    
    
				// 发现待插入的结点的值和当前节点相同
				return false;
			}
		}
	}
private:
	Node* _root = nullptr;
};

2. Insert

The parent node we used earlier works wonders at this time, because the last step of our cur is definitely to go to nullptr, which is an empty node, so next we use the structure of parent and cur to perform link insertion relationships:

	// 插入数值
	cur = new Node(kv);
	// 父亲结点小于待插入结点
	if (parent->_kv.first < kv.first)
	{
    
    
		// 往右子树插入
		parent->_right = cur;
	}
	// 父亲结点小于待插入结点
	else
	{
    
    
		// 往左子树插入
		parent->_left = cur;
	}
	// 当前节点的父节点为parent结点,更新一下防止关系乱
	cur->_parent = parent;

3. Control the balance – see if you need to modify the balance factor

After judging whether the insertion is successful, is it necessary to judge the update of the balance factor?

Whether the balance factor is updated depends on whether the height of the left and right subtrees of the node has changed. Therefore, after inserting a node, the balance factor of the node's ancestor node may need to be updated.

Let’s use a diagram to explain the update:

Insert image description here
After we insert the node, we find that some updated nodes will not only affect the parent node, but also the parent node of its parent node, and even affect it further up. In other words, this updated node will affect the parent node.

Rules for updating balance factors:
1. New additions are on the right, parent->_bf++
2. New additions are on the left, parent- >_bf–

However, we discovered a phenomenon. This new addition on the left or right seems to have a different impact on the parent node. The node I added is on the branch of a separate left/right subtree, then the parent node must also be updated. If Say the height of my tree has not changed, and the balance factor of the grandpa node will not change, so we need to determine the balance factor of the parent node! We have the following conclusions:

1. If the parent's balance factor is equal to -1 or 1, it indicates that it is still necessary to continue to update the balance factor
2. If the parent's The balance factor is equal to 0; indicating that there is no need to update the balance factor upwards
3. If the balance factor of the parent is equal to -2 or 2; it is already unbalanced and needs to be Rotateprocess!
4. If the balance factor of the parent is greater than 2 or less than -2, it means that the previously inserted is not an AVL tree. If you check forward, there must be a wrong step

Insert image description here

Insert image description here

Because if you need to continue to update the node after updating the parent, you need to provide an iterative code to continue to update, so the logic of continuing to update is as follows:

	cur = parent;
	parent = parent->_parent; 

4. Rotation (determine whether rotation is necessary, determine whether it is left or right rotation, determine whether it is single or double rotation)

When the balance factor reaches 2/-2, the subtree must be rotated, but the principle must also be followed:
Rotate into a balanced tree
Maintain the rules of the search tree

There are four major situations of rotation, which we need to classify:

1. When the balance factor of parent is 2 and the balance factor of cur is 1, perform left single rotation
2. When the balance factor of parent is -2, the balance of cur When the factor is -1, perform a right single rotation
3. When the balance factor of parent is -2 and the balance factor of cur is 1, perform a left-right double rotation
4. When the balance factor of parent is 2 and the balance factor of cur is -1, perform right and left double rotation

Note: After the rotation, there is no need to update the balance factor upwards, because the height of the subtree itself has not changed and will not affect the balance factor of the parent node at all.

		// 控制平衡
		// 平衡因子的平衡
		while (parent)
		{
    
    
			if (cur == parent->_right)
			{
    
    
				parent->_bf++;
			}
			else
			{
    
    
				parent->_bf--;
			}

			if (parent->_bf == 1 || parent->_bf == -1)
			{
    
    
				//继续往上更新
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 0)
			{
    
    
				//over
				break;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
    
    
				//旋转
				if (parent->_bf == 2 && cur->_bf == 1)
				{
    
    
					// 左单旋
					RoateL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == -1)
				{
    
    
					// 右单旋
					RoateR(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
    
    
					// 左右单旋
					RoateLR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
    
    
					// 右左单旋
					RoateRL(parent);
				}
			}
			else
			{
    
    
				assert(false);
			}
		}

Insert total code

// 插入
	bool Insert(const pair<K, V>& kv)
	{
    
    
		// 找
		// 1、待插入结点key比当前结点小就往左子树跑
		// 2、待插入结点key比当前结点大就往右子树跑
		// 3、待插入结点key和当前结点的值相等就显示插入失败
		// 刚开始进去为空树
		if (_root == nullptr)
		{
    
    
			_root = new Node(kv);
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		// 不是空树
		while (cur)
		{
    
    
			if (cur->_kv.first < kv.first)
			{
    
    
				parent = cur; // 保存一下cur原本的结点
				cur = cur->_right; // 往右跑
			}
			else if (cur->_kv.first > kv.first)
			{
    
    
				parent = cur; // 保存一下cur原本的结点
				cur = cur->_left;
			}
			else
			{
    
    
				// 发现待插入的结点的值和当前节点相同
				return false;
			}
		}

		// 插入数值
		cur = new Node(kv);
		// 父亲结点小于待插入结点
		if (parent->_kv.first < kv.first)
		{
    
    
			// 往右子树插入
			parent->_right = cur;
		}
		// 父亲结点小于待插入结点
		else
		{
    
    
			// 往左子树插入
			parent->_left = cur;
		}
		// 当前节点的父节点为parent结点,更新一下防止关系乱
		cur->_parent = parent;

		// 控制平衡
		// 平衡因子的平衡
		while (parent)
		{
    
    
			if (cur == parent->_right)
			{
    
    
				parent->_bf++;
			}
			else
			{
    
    
				parent->_bf--;
			}

			if (parent->_bf == 1 || parent->_bf == -1)
			{
    
    
				//继续往上更新
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 0)
			{
    
    
				//over
				break;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
    
    
				//旋转
				if (parent->_bf == 2 && cur->_bf == 1)
				{
    
    
					// 左单旋
					RoateL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == -1)
				{
    
    
					// 右单旋
					RoateR(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
    
    
					// 左右单旋
					RoateLR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
    
    
					// 右左单旋
					RoateRL(parent);
				}
				break;
			}
			else
			{
    
    
				assert(false);
			}
		}
		return true;
	}

The new node is inserted to the left of the higher left subtree - left-left: right single rotation

Insert image description here

Abstract diagram below:

Insert image description here

Right single rotation steps:

1. sublr becomes the left subtree of parent (the relationship between sublr and parent)
2. parent becomes the right subtree of subl (the relationship between parent and subl, please note that subl may is empty)
3. subl becomes the root node (note whether parent is the root of the subtree or the root of the entire tree)
4. Finally update the balance factor< /span>

Insert image description here

Update balance factor:
Insert image description here

Code:

	// 右单旋
	void RoateR(Node* parent)
	{
    
    
		// 三叉链
		Node* subl = parent->_left;
		Node* sublr = subl->_right;
		Node* ppnode = parent->_parent;


		//sublr和parent之间的关系
		parent->_left = sublr;
		if (sublr)
			sublr->_parent = parent;

		//subl和parent的关系
		subl->_right = parent;
		parent->_parent = subl;


		//ppnode 和 subl的关系
		if (ppnode == nullptr)
		{
    
    
			_root = subl;
			subl->_parent = nullptr;
		}
		else
		{
    
    
			if (ppnode->_left == parent)
			{
    
    
				ppnode->_left = subl;
			}
			else
			{
    
    
				ppnode->_right = subl;
			}
			subl->_parent = ppnode;
		}

		//更新平衡因子
		subl->_bf = parent->_bf = 0;
	}

The new node is inserted to the right of the higher right subtree – right-right: left-handed rotation

Insert image description here

Abstract diagram below:

Insert image description here

Left single rotation steps:

1. subrl becomes the right subtree of parent (the relationship between subl and parent, please note that subl may be empty)
2. parent becomes the left subtree of subr (parent and parent sublr relationship)
3. subr becomes the root node (note whether parent is the root of the subtree or the root of the entire tree)
4. Finally update the balance factor

Insert image description here
Three questions:
1. Is parent the root of the entire tree or just the node of the root of the subtree?
2. Determine whether the parent was originally on the left or right of ppnode?
3. When subrl is empty?

Balance factor update:
Insert image description here

Code:

	// 左单旋
	void RoateL(Node* parent)
	{
    
    
		// 三叉链
		Node* subr = parent->_right;
		Node* subrl = subr->_left;
		Node* ppnode = parent->_parent;

		// subrl与parent的关系
		parent->_right = subrl;
		if (subrl)
			subrl->_parent = parent;

		// subl和parent的关系
		subr->_left = parent;
		parent->_parent = subr;

		// ppnode和subr的关系
		if (ppnode == nullptr)
		{
    
    
			_root = subr;
			subr->_parent = nullptr;
		}
		else
		{
    
    
			if (ppnode->_left == parent)
			{
    
    
				ppnode->_left = subr;
			}
			else
			{
    
    
				ppnode->_right = subr;
			}
			subr->_parent = ppnode;
		}

		// 更新平衡因子
		subr->_bf = parent->_bf = 0;
	}

left and right double rotation

If the node shape is the same as the following, what we need to update is that we need to straighten it first, and then break off the upper section.
Insert image description here

1. Insert new node first
Insert image description here

2. Straighten – use 30 as the rotation point to rotate left

Insert image description here

3. Fold – Use 90 as the rotation point to turn right

Insert image description here

Steps for left and right double rotation

1. Use subl as the rotation point to rotate leftward
2. Use parent as the rotation point to rotate rightward
3. Update the balance factor a>

The left and right double spins satisfy the properties of a binary search tree

After left and right double rotation, in fact, let the left subtree and right subtree of subLR serve as the right subtree and left subtree of subl and parent respectively, then let subl and parent serve as the left and right subtrees of sublr respectively, and finally let sublr serve as The root of the entire subtree (understood in conjunction with the graph).

1. The node in the left subtree of sublr itself is larger than the value of subL, so it can be used as the right subtree of subl.
2. The node in the right subtree of sublr itself is smaller than the value of parent, so it can be used as the left subtree of parent.
3. After step 1/2, the values ​​of the nodes in subL and its subtree are smaller than the value of sublr, and the values ​​of the nodes in parent and its subtree are smaller than The value of sublr is large, so they can be used as the left and right subtrees of sublr respectively.

Update balance factor

The update of the balance factor is divided into the following three situations depending on the original balance factor of sublr:

1. When the original balance factor of sublr is -1, the balance factors of left and right double spin parent, subl, and sublr are updated to 1, 0, and 0 respectively.

Insert image description here

2. When the original balance factor of sublr is 1, the balance factors of parent, subl, and sublr of the left and right double spins are updated to 0, -1, and 0 respectively.

Insert image description here

3. When the original balance factor of sublr is 0, the balance factors of parent, subl, and sublr of the left and right double spins are updated to 0, 0, and 0 respectively.

Insert image description here

After the left and right double rotation, the height of the tree has not changed, so there is no need to continue to update the balance factor upwards.

code
	// 左右双旋
	void RoateLR(Node* parent)
	{
    
    
		Node* subl = parent->_left;
		Node* sublr = subl->_right;
		int bf = sublr->_bf;

		//subl节点左单旋
		RoateL(subl);

		//parent节点进行右单旋
		RoateR(parent);

		//更新平衡因子
		if (bf == 1)
		{
    
    
			sublr->_bf = 0;
			subl->_bf = -1;
			parent->_bf = 0;
		}
		else if (bf == -1)
		{
    
    
			sublr->_bf = 0;
			subl->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == 0)
		{
    
    
			sublr->_bf = 0;
			subl->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
    
    
			assert(false);
		}
	}

Right-left double rotation

Insert image description here

1. Insert new node

Insert image description here

2. Straighten - rotate right with 90 as the rotation node

Insert image description here

3. Fold it down - rotate left with 30 as the rotation node

Insert image description here

Steps for right and left double rotation

1. Use the node of subr to rotate to the right
2. Use the node of parent to rotate to the left
3. Control the balance factor a>

The right and left double post-spin satisfies the properties of a binary search tree

After right-left double rotation, in fact, let the left subtree and right subtree of subrl serve as the right subtree and left subtree of parent and subr respectively, then let parent and subr serve as the left and right subtrees of subrl respectively, and finally let subrl serve as The root of the entire subtree

1. The node in the left subtree of subrl itself is larger than the value of parent, so it can be used as the right subtree of parent.
2. The node in the right subtree of subrl itself is smaller than the value of subr, so it can be used as the left subtree of subr.
3. After step 1/2, the values ​​of the nodes in parent and its subtree are smaller than the value of subrl, while the values ​​of the nodes in subr and its subtree are smaller than The value of subrl is large, so they can be used as the left and right subtrees of subrl respectively.

Update balance factor

The update of the balance factor is divided into the following three situations depending on the original balance factor of sublr:

1. When the original balance factor of subrl is -1, the balance factors of parent, subr, and subrl of the right and left double supinations are updated to 0, 1, and 0 respectively.

Insert image description here

2. When the original balance factor of subrl is 1, the balance factors of left and right double supination parent, subr, and subrl are updated to -1, 0, and 0 respectively.

Insert image description here

3. When the original balance factor of subrl is 0, the balance factors of parent, subr, and subrl of the left and right double spins are updated to 0, 0, and 0 respectively.

Insert image description here

code
	// 右左双旋
	void RoateRL(Node* parent)
	{
    
    
		Node* subr = parent->_right;
		Node* subrl = subr->_left;
		int bf = subrl->_bf;

		//subR右单旋
		RoateR(subr);

		//parent左单旋
		RoateL(parent);

		// 更新平衡因子
		if (bf == 1)
		{
    
    
			subrl->_bf = 0;
			subr->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
    
    
			subrl->_bf = 0;
			subr->_bf = 1;
			parent->_bf = 0;
		}
		else if (bf == 0)
		{
    
    
			subrl->_bf = 0;
			subr->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
    
    
			assert(false);
		}
	}

4. Searching the AVL tree

The search in the AVL tree is the search in the binary search tree:
1. If the current tree is empty, nullptr is returned
2. To be found If the node is smaller than the current node, go to the left subtree
3. The node to be found is larger than the current node, go to the right subtree
4. Wait If the value of the search node is the same as the value of the current node, return the current node

	// AVL树的查找
	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;
	}

5. Modification of AVL tree

Two methods:

direct amendment method

1. Use the Find function we wrote to find the point where the current key value is key
2. Modify the value where the current key value is value

	// AVL树的修改
	bool Modify(const K& key, const V& value)
	{
    
    
		// 1、先找到利用Find函数
		Node* ret = Find(key);
		if (ret == nullptr)
			return false;
		// 2、修改value值
		ret->_kv.second = value;
		return true;
	}

Use the insert function to modify

Why do we need to use the insertion function to modify? This is because we search when inserting, insert at a specified position, and then judge whether the balance factor needs to be rotated!

	// AVL树的修改2
	pair<Node*, bool> Modify(const pair<K, V>& kv)
	{
    
    
		// 找
		// 1、待插入结点key比当前结点小就往左子树跑
		// 2、待插入结点key比当前结点大就往右子树跑
		// 3、待插入结点key和当前结点的值相等就显示插入失败
		// 刚开始进去为空树
		if (_root == nullptr)
		{
    
    
			_root = new Node(kv);
			return make_pair(_root, true);
		}

		Node* parent = nullptr;
		Node* cur = _root;
		// 不是空树
		while (cur)
		{
    
    
			// 待插入结点比当前结点小,往右子树跑
			if (cur->_kv.first < kv.first)
			{
    
    
				parent = cur; // 保存一下cur原本的结点
				cur = cur->_right; // 往右跑
			}
			else if (cur->_kv.first > kv.first)
			{
    
    
				parent = cur; // 保存一下cur原本的结点
				cur = cur->_left;
			}
			else
			{
    
    
				// 发现待插入的结点的值和当前节点相同
				return make_pair(cur, false);
			}
		}

		// 插入数值
		cur = new Node(kv);
		Node* newnode = cur;
		// 父亲结点小于待插入结点
		if (parent->_kv.first < kv.first)
		{
    
    
			// 往右子树插入
			parent->_right = cur;
		}
		// 父亲结点小于待插入结点
		else
		{
    
    
			// 往左子树插入
			parent->_left = cur;
		}
		// 当前节点的父节点为parent结点,更新一下防止关系乱
		cur->_parent = parent;

		// 控制平衡
		// 平衡因子的平衡
		while (parent)
		{
    
    
			if (cur == parent->_right)
			{
    
    
				parent->_bf++;
			}
			else
			{
    
    
				parent->_bf--;
			}

			if (parent->_bf == 1 || parent->_bf == -1)
			{
    
    
				//继续往上更新
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 0)
			{
    
    
				//over
				break;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
    
    
				//旋转
				if (parent->_bf == 2 && cur->_bf == 1)
				{
    
    
					// 左单旋
					RoateL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == -1)
				{
    
    
					// 右单旋
					RoateR(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
    
    
					// 左右单旋
					RoateLR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
    
    
					// 右左单旋
					RoateRL(parent);
				}
				break;
			}
			else
			{
    
    
				assert(false);
			}
		}
		return make_pair(newnode, true);
	}

6. Overloading of AVL tree

1. Call the key pair value of the insertion function
2. Take out the key of the node
3. Return the reference of the value

V() is an anonymous object variable, because we are not sure what it is, and we are not sure whether the second key pair value is false or true, so we use anonymous, because if the key is not in the tree, insert the key first, V() , and then return the value reference. If the key is already in the tree, return the value reference of the key pair to the value pair key!

	// AVL[]重载
	V& operator[](const K& key)
	{
    
    
		// 键对值
		pair<Node*, bool> ret = Insert(make_pair(key, V()));
		// 拿出结点的node
		Node* node = ret.first;
		// 返回该结点的value引用值
		return node->_kv.second;
	}

7. Deletion of AVL tree (difficult)

Insert image description here

The preliminary operation of deletion is the same as the preliminary operation of our insertion. We must first find the node to be deleted, that is, we need to find the node corresponding to the key value in the tree and find the node to be deleted. The node method is the same as the binary search tree:
1. First find the node to be deleted.
2. If the left and right subtrees of the node to be deleted are not empty, you need to use the replacement method (the rightmost node of the left subtree or the leftmost node of the right subtree) to delete.

Replacement method: Find the position of the node that needs to be deleted, find the node with the maximum value in its left subtree, or find it in its right subtree The node with the smallest value, and then change the key value and value value of the node to be deleted to the value of the deleted node.

When we find a node to be deleted, we update the balance factor first, because if we delete it first, the balance factor will be very messy, so we need to update the balance factor first, and then perform the deletion operation.

The rules for updating the balance factor are as follows:
The deleted node is on the right side of the parent, and the balance factor of the parent is − −.
The deleted node is to the left of the parent, and the balance factor of the parent + + .

After updating the balance factor of a node, the following judgment needs to be made:
If the balance factor of the parent is equal to -1 or 1 , there is no need to continue to update the balance factor.
If the parent's balance factor is equal to 0, the balance factor needs to be updated upwards.
If the parent's balance factor is equal to -2 or 2, it means that the subtree with the parent node as the root node is already unbalanced and needs to be performed Rotationprocessing.

Legend to determine whether to update upwards and whether rotation processing is required:
Insert image description here

When rotating it, we divide it into the followingsix situations:
1 , when the balance factor of the parent is -2 and the balance factor of the left child of the parent is -1, perform a right single rotation.
2. When the balance factor of the parent is -2 and the balance factor of the left child of the parent is 1, perform left and right double rotation.
3. When the balance factor of the parent is -2 and the balance factor of the left child of the parent is 0, a right single rotation is also performed. There is no need to update the balance factor upwards, update the current!
4. When the balance factor of the parent is 2 and the balance factor of the parent's right child is -1, perform a right-left double rotation.
5. When the balance factor of the parent is 2 and the balance factor of the parent's right child is 1, perform a single left rotation.
6. When the balance factor of the parent is 2 and the balance factor of the parent's right child is 0, a single left rotation is also performed without updating the balance factor upwards and updating the current.

We need to update the balance factor after the rotation!

After updating the balance factor, we actually need to delete the operation:
1. The left subtree of the actually deleted node is empty, and the parent is linked to the right of the actually deleted node. Subtree, delete the node that actually needs to be deleted.
2. The right subtree of the actually deleted node is empty, the parent is linked to the left subtree of the actually deleted node, and the node that actually needs to be deleted is deleted.
3. If the left and right subtrees of the actually deleted node are not empty, you need to use the substitution method (already replaced before) to replace the node and then delete it.

	// AVL树的删除
	bool Erase(const K& key)
	{
    
    
		// 用于遍历二叉树找结点
		Node* parent = nullptr;
		Node* cur = _root;
		// 用于标记实际的删除结点及其父结点
		Node* delparentpos = nullptr;
		Node* delpos = nullptr;
		// 先找到
		while (cur)
		{
    
    
			// 所给key值小于当前节点的值 -- 往左树走
			if (key < cur->_kv.first)
			{
    
    
				parent = cur;
				cur = cur->_left;
			}
			// 所给key值大于当前结点的值 -- 往右树走
			else if (key > cur->_kv.first)
			{
    
    
				parent = cur;
				cur = cur->_right;
			}
			// 找到了
			else
			{
    
    
				// 左子树为空
				if (cur->_left == nullptr)
				{
    
    
					// 待删除结点是根节点
					if (cur == _root)
					{
    
    
						// 让根节点的右子树作为新的结点
						_root = _root->_right;
						if (_root)
							_root->_parent = nullptr;
						delete cur; // 删除原节点
						return true;
					}
					else // 不是根节点
					{
    
    
						delparentpos = parent; // 标记当前待删除结点的父节点
						delpos = cur; // 标记当前待删除的结点
					}
					break; // 删除结点有祖先的结点,需要更新平衡因子
				}
				// 右子树为空
				else if (cur->_right == nullptr)
				{
    
    
					// 待删除结点是根节点
					if (cur == _root)
					{
    
    
						// 让根节点的左子树作为新的结点
						_root = _root->_left;
						if (_root)
							_root->_parent = nullptr;
						delete cur; // 删除原节点
						return true;
					}
					else // 不是根节点
					{
    
    
						delparentpos = parent; // 标记当前待删除结点的父节点
						delpos = cur; // 标记当前待删除的结点
					}
					break; // 删除结点有祖先的结点,需要更新平衡因子
				}
				// 左右子树都不为空
				else
				{
    
    
					// 替换法
					// 寻找待删除结点的右子树中的最小值
					Node* minparent = cur;
					Node* minright = cur->_right;
					while (minright->_left)
					{
    
    
						minparent = minright; // 记录一下父节点
						minright = minright->_left; // 往左子树走
					}
					cur->_kv.first = minright->_kv.first;// 将待删除结点first替换为右子树的最小值
					cur->_kv.second = minparent->_kv.second;// 将待删除结点second替换为右子树的最小值
					// 记录一下要删除的父节点
					delparentpos = minparent;
					// 记录一下实际要删除的结点
					delpos = minright;
					break; // 祖先结点的平衡因子需要改变
				}
			}
		}
		// 没有被修改过,说明没找到当前要删除的结点
		if (delparentpos == nullptr)
			return false;

		// 记录当前要删除结点和当前要删除结点的父节点
		Node* del = delpos;
		Node* delP = delparentpos;

		// 更新平衡因子
		while (delpos != _root) // 最坏情况是一路更新到根节点
		{
    
    
			// 删除结点在右子树
			if (delpos == delparentpos->_right)
				delparentpos->_bf--;
			// 删除结点在左子树
			else if(delpos == delparentpos->_left)
				delparentpos->_bf++;
			// 判断是否需要旋转
			if (delparentpos->_bf == 0)
			{
    
    
				// 向上更新
				delpos = delparentpos;
				delpos = delpos->_parent;
			}
			else if (delparentpos->_bf == -1 || delparentpos->_bf == 1)
			{
    
    
				// 要删除的父节点的平衡因子为1/-1,无需向上更新平衡因子
				break;
			}
			else if(delparentpos->_bf == 2 || delparentpos->_bf == -2)
			{
    
    
				// 旋转
				// 1、右旋
				if (delparentpos->_bf == -2 && delparentpos->_left->_bf == -1)
				{
    
    
					// 记录一下右旋后的根节点
					Node* right_root = delparentpos->_left;
					// 右旋
					RoateR(delparentpos);
					// 更新根节点
					delparentpos = right_root;
				}
				// 2、左右双旋
				else if (delparentpos->_bf == -2 && delparentpos->_left->_bf == 1)
				{
    
    
					// 记录一下左右双旋后的根节点
					Node* right_left_root = delparentpos->_left->_right;
					// 左右双旋
					RoateLR(delparentpos);
					// 更新根节点
					delparentpos = right_left_root;
				}
				// 3、右单旋
				else if (delparentpos->_bf == -2 && delparentpos->_left->_bf == 0)
				{
    
    
					// 不需要往上更新节点
					Node* right_root = delparentpos->_left;
					RoateR(delparentpos);
					delparentpos = right_root;
					// 更新当前平衡因子
					delparentpos->_bf = 1;
					delparentpos->_right->_bf = -1;
					break;
				}
				// 4、右左单旋
				else if (delparentpos->_bf == -1 && delparentpos->_right->_bf == -1)
				{
    
    
					// 记录一下当前要删除的结点
					Node* right_left_root = delparentpos->_right->_left;
					RoateRL(delparentpos);
					delparentpos = right_left_root;
				}
				// 5、左单旋
				else if (delparentpos->_bf == 2 && delparentpos->_right->_bf == 1)
				{
    
    
					Node* left_root = delparentpos->_right;
					RoateL(delparentpos);
					delparentpos = left_root;
				}
				// 6、左单旋
				else if (delparentpos->_bf == 2 && delparentpos->_parent == 0)
				{
    
    
					// 不需要向上更新节点
					Node* left_root = delparentpos->_right;
					RoateL(delparentpos);
					delparentpos = left_root;
					// 更新当前平衡因子
					delparentpos->_bf = -1;
					delparentpos->_left->_bf = 1;
					break;
				}
			}
			else
			{
    
    
				assert(false);
			}

			// 继续向上更新
			delpos = delparentpos;
			delparentpos = delparentpos->_parent;
		}

		// 实际删除操作
		// 左子树为空
		if (del->_left == nullptr)
		{
    
    
			// 实际删除的结点刚好是其父节点的左孩子
			if (del == delP->_left)
			{
    
    
				delP->_left = del->_right;
				if (del->_right)
					del->_right->_parent = parent;
			}
			// 实际删除的结点刚好是其父节点的右孩子
			else
			{
    
    
				delP->_right = del->_right;
				if (del->_right)
					del->_right->_parent = parent;
			}
		}
		// 右子树为空
		else
		{
    
    
			// 实际删除的结点刚好是其父节点的左孩子
			if (del == delP->_left)
			{
    
    
				delP->_left = del->_left;
				if (del->_left)
					del->_left->_parent = parent;
			}
			// 实际删除的结点刚好是其父节点的右孩子
			else
			{
    
    
				delP->_right = del->_left;
				if (del->_left)
					del->_left->_parent = parent;
			}
		}
		// 删除实际结点
		delete del;
		return true;
	}

8. Verify AVL tree

The AVL tree adds balance restrictions on the basis of the binary search tree. Therefore, to verify the AVL tree, you can divide it into two steps:

  1. Verify that it is a binary search tree
    If an in-order traversal can obtain an ordered sequence, it is a binary search tree
  2. Verify that it is a balanced tree
    The absolute value of the height difference between the subtrees of each node does not exceed 1 (note that if there is no balance factor in the node)
    Is the node's balance factor calculated correctly?
	// 中序遍历验证是个搜索树
	void _InOrder(Node* root)
	{
    
    
		if (root == nullptr)
		{
    
    
			return;
		}

		_InOrder(root->_left);
		_InOrder(root->_right);
		cout << root->_kv.first << ":" << root->_kv.second << endl;

	}

	void InOrder()
	{
    
    
		_InOrder(_root);
	}

First verify whether it is a binary search tree

The AVL tree adds balance restrictions on the basis of the binary search tree. That is to say, the AVL tree is also a binary search tree. Therefore, we can first obtain the in-order traversal sequence of the binary tree to determine whether the binary tree is a binary search tree. .

	// 中序遍历验证是个搜索树
	void _InOrder(Node* root)
	{
    
    
		if (root == nullptr)
		{
    
    
			return;
		}

		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}

	void InOrder()
	{
    
    
		_InOrder(_root);
	}

Verify again whether it is a balanced tree (the height of the subtrees on both sides)

In-order order can only prove that it is a binary search tree. To prove that the binary tree is an AVL tree, we need to verify the balance of the binary tree. In the process, we can check whether the balance factor in each node is correct.

Usingpost-order traversal, the traversal steps are as follows:
1. Start calculation from the leaf node The height of each subtree in each lesson (the height of each subtree = the larger value of the heights in the left and right subtrees + 1) and record it!
2. First determine whether the left subtree is a balanced binary tree.
3. Then determine whether the right subtree is a balanced binary tree.
4. If the left and right subtrees are both balanced binary trees, return the height of the current subtree to the previous level, and continue to determine whether the subtree of the previous level is a balanced binary tree until the root is determined. . (If during the judgment process, a certain subtree is not a balanced binary tree, then the tree is not a balanced binary tree)

Insert image description here

	// 判断是不是平衡树
	bool IsBalance()
	{
    
    
		return IsBalance(_root);
	}

	bool IsBalance(Node* root)
	{
    
    
		if (root == nullptr)
			return true;
		// 后序遍历
		int leftHight = Height(root->_left);
		int rightHight = Height(root->_right);
		// 计算两个树的高度之差即可,只要是高度之差不等于根节点的平衡因子的值
		if (rightHight - leftHight != root->_bf)
		{
    
    
			cout << "平衡因子异常:" << root->_kv.first << "->" << root->_bf << endl;
			return false;
		}
		else
		{
    
    
			cout << "没问题" << endl;
		}
		// 递归判断条件
		return abs(rightHight - leftHight) < 2
			&& IsBalance(root->_left)
			&& IsBalance(root->_right);
	}

9. Performance of AVL tree

The AVL tree is an absolutely balanced binary search tree, which requires that the absolute value of the height difference between the left and right subtrees of each node does not exceed 1. This can ensure efficient time complexity during query, that is, < /span> l o g 2 ( N ) log_2(N) log2(N). However, if you want to make some structural modifications to the AVL tree, the performance is very low. For example, when inserting, you need to maintain its absolute balance, and the number of rotations is relatively high. What is even worse is that when deleting, the rotation may continue to the root. s position. Therefore: If you need a data structure with efficient query and order, and the number of data is static (that is, it will not change), you can consider the AVL tree, but if a structure is frequently modified, it is not suitable.


Guess you like

Origin blog.csdn.net/m0_70088010/article/details/132813995