[C/C++] Detailed introduction of STL container 4--AVL tree and partial simulation implementation

This paper introduces AVL tree and simulates its core functions.

Catalog

1. The concept of AVL tree

  2. Definition of AVL tree node

3. Insertion of AVL Tree

4. Rotation of AVL Trees

1. The new node is inserted to the left of the higher left subtree - left left: right single-handed

2. Insert the new node to the right of the higher right subtree - Right: Left Single Rotation

3. The new node is inserted to the right of the higher left subtree - left or right: left-handed and right-handed first

4. New node inserts left side of higher right subtree - right left: right single-handed first, then left single-handed

5. Summary

5. Performance of AVL trees

Complete Code

1. The concept of AVL tree

Although the binary search tree can reduce the efficiency of searching, if the data is ordered or close to the ordered binary search tree will degenerate into a single branch tree, searching for elements is equivalent to searching for elements in a sequential table, which is inefficient. Therefore, two Russian mathematicians, G.M.Adelson-Velskii and E.M.Landis, invented in 1962 a way to solve the above problem: when new nodes are inserted into a binary search tree, the height of the tree can be reduced by ensuring that the absolute difference between the left and right subtrees of each node does not exceed 1 (the nodes in the tree need to be adjusted), thereby reducing the average search length.
An AVL tree or an empty tree is a binary search tree with the following properties:

  1. Its left and right subtrees are AVL trees.
  2. The absolute value of the difference between left and right subtree heights (for short, the balance factor) does not exceed 1 (-1/0/1).

  By increasing the balance factor (right subtree height-left subtree height) for each node, the absolute difference in height can be guaranteed not to exceed 1.

If a binary search tree is highly balanced, it is an AVL tree. If it has n nodes, its height can be kept at, search time complexity.

  2. Definition of AVL tree node

Definition of AVL tree node:

template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;

	int _bf; //Balance factor

	pair<K, V> _kv;		//Packaging two data into one using a structure template

	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		,_parent(nullptr)
		,_bf(0)
		,_kv(kv)
	{}
};

3. Insertion of AVL Tree

AVL tree is based on the binary search tree and introduces a balance factor, so AVL tree can also be considered as a binary search tree. Then the insertion process of AVL tree can be divided into two steps:

  1. First, insert nodes into the AVL tree according to the rules of the binary search tree.
  2. By adjusting the balance factor of nodes, the balance of AVL trees may be destroyed after new nodes are inserted. At this time, the balance factor needs to be updated and whether the balance of AVL trees is destroyed is detected.

After cur insertion, parent's balance factor must be adjusted. Before cur insertion, pParent's balance factor is divided into three cases: -1, 0, 1, divided into the following two cases:

  1. If the pCur is inserted to the left of the pParent, simply give the pParent a balance factor of -1.
  2.   If pCur is inserted to the right of the pParent, simply give the pParent a balance factor of + 1;

In this case, there are three possible balance factors for pParent: 0, plus or minus 1, plus or minus 2

  1. If the balance factor of pParent is 0, it means that the balance factor of pParent before insertion is positive or negative 1, then it is adjusted to 0 after insertion, which satisfies the nature of AVL tree and inserts successfully.
  2. If the balance factor of pParent is positive or negative 1, it means that the balance factor of pParent before insertion must be 0. When pParent is updated to positive or negative after insertion, the height of the tree rooted by pParent increases and needs to be updated upwards.
  3. If the balance factor of the pParent is positive or negative 2, the balance factor of the pParent violates the nature of the balance tree and needs to be rotated.

  The code is as follows (the rotation section is described in the next section):

	bool Insert(const pair<K, V> kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);

			return ture;
		}


		//1. Find the target location and insert a new node
		Node* parent = _root, cur = _root;
		while (cur)
		{
			if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(kv);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}

		//Control balance
		//1. Update Balance Factor
		//2. Unbalanced rotates
		while (cur != _root)
		{
			if (parent->_left == cur) {			//Processing, left plus right minus
				parent->_bf++;	
			}
			else {
				parent->_bf--;
			}

			if (parent->_bf == 0) {
				break;						//Originally = -1/1, and added to the empty space on the other side, height does not change
			}
			else if(parent->_bf == 1 || parent->_bf == -1)
			{
				//Height changes, need to continue changing parent node
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				//Need to rotate
			}
		}

		return true;
	}

4. Rotation of AVL Trees

If you insert a new node into an AVL tree that was originally balanced, this may cause imbalance, and you must adjust the tree structure to make it balanced. There are four types of rotation for AVL trees, depending on where the nodes are inserted:

1. The new node is inserted to the left of the higher left subtree - left left: right single-handed

The AVL tree is balanced before insertion. New nodes are inserted into the left subtree of 30 (note: this is not the left child). The left subtree of 30 adds a layer, which results in an unbalanced binary tree with 60 roots. To make 60 balanced, only the height of the left subtree of 60 can be reduced by one layer, and the right subtree can be increased by one layer, that is, the left subtree is lifted up, so 60 turns down because 60 is larger than 30. It can only be placed in the right subtree of 30, and if 30 has a right subtree, the value of the right subtree root must be greater than 30, less than 60, it can only be placed in the left subtree of 60, after the rotation is completed, the balance factor of the node can be updated. There are several situations to consider during the rotation process:

  1. The right child of 30 nodes may or may not exist;
  2. 60 may be a root node or a subtree. If it is a root node, after the rotation is complete, the root node will be updated. If it is a subtree, it may be the left subtree of a node or the right subtree.

The code is as follows:

	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_left;
		Node* parentP = parent->_parent;

		if (subLR)							//The right subtree of the left subtree connects to the right of the parent
			subLR->_parent = parent;		
		
		parent->_left = subLR;
		subL->_right = parent;
		parent->_parent = subL;

		// If parent is the root node, the root new pointer to the root node
		if (parent == _root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			// If a parent is a subtree, it may be the left subtree of its parent or the right subtree
			if (parentP->_left == parent)
				parentP->_left = subL;
			else
				parentP->_right = subL;

			subL->_parent = parentP;
		}

		// Update the balance factor of some nodes according to the adjusted structure
		subL->_bf = parent->_bf = 0;
	}

2. Insert the new node to the right of the higher right subtree - Right: Left Single Rotation

Logic can refer to 1, where the code is given directly:

void RotateL(Node* parent)
	{
		Node* subR = parent->_left;
		Node* subRL = subL->_left;
		Node* parentP = parent->_parent;

		if (subRL)
			subRL->_parent = parent;

		parent->_right = subRL;
		subR->_left = parent;
		parent->_parent = subR;

		// If parent is the root node, the root new pointer to the root node
		if (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			// If a parent is a subtree, it may be the left subtree of its parent or the right subtree
			if (parentP->_left == parent)
				parentP->_left = subR;
			else
				parentP->_right = subR;

			subR->_parent = parentP;
		}

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

3. The new node is inserted to the right of the higher left subtree - left or right: left-handed and right-handed first

  Turn the double-spin into single-spin and then rotate, that is, left single-spin for 30, right single-spin for 90, and then consider the update of the balance factor after the rotation is completed.

Before rotation, the balance factor of 60 may be -1/0/1. After rotation is completed, the balance factor of other nodes is adjusted as appropriate.

  Balance factor updates in three cases:

  1. b Subtree height changes to h+1, causing double rotation;
  2. c Subtree height changes to h+1, causing double rotation;
  3. h == 0, a,b,c,d subtrees exist, 60 is new.

The code is as follows:

	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->__right;
		int bf = subR->_bf;

		RotateL(parent->_left);
		RotateR(parent);

		if (bf == -1)				//Situation 1:
		{
			subL->_bf = 0;
			parent->_bf = 1;
			subLR->_bf = 0;
		}
		else if (bf == 1)				//Scenario 2:
		{
			parent->_bf = 0;
			subL->_bf = -1;
			subLR->_bf = 0;
		}
		else if (bf == 0)			//Situation three:
		{
			parent->_bf = 0;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

4. New node inserts left side of higher right subtree - right left: right single-handed first, then left single-handed

The code is as follows:

void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subL->_left;
		int bf = subR->_bf;	//Before rotating, save the balance factor of SubLR, and after rotating, adjust the balance factor of other nodes according to this balance factor

		RotateR(parent->_right);		//First Right
		RotateL(parent);

		//Update Balance Factor
		if (bf == 1)				//Situation 1:
		{
			subL->_bf = 0;
			parent->_bf = -1;
			subLR->_bf = 0;
		}
		else if (bf == -1)				//Scenario 2:
		{
			parent->_bf = 0;
			subL->_bf = 1;
			subLR->_bf = 0;
		}
		else if (bf == 0)			//Situation three:
		{
			parent->_bf = 0;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

5. Summary

  If the subtree root on Parent is unbalanced, that is, the parent's balance factor is 2 or -2, consider the following  

  1. Parent's balance factor is 2, which indicates the right subtree height of the parent and sets the root of the right subtree of the parent as SubR
    1. When the balance factor for SubR is 1, perform a left-handed spin
    2. When SubR's balance factor is -1, perform right-left double-handed
  2. The parent's balance factor is -2, indicating the left subtree height of the parent and setting the root of the parent's left subtree to SubL
    1. When SubL's balance factor is -1, perform right-handed
    2. When SubL's balance factor is 1, double-rotation is performed

When the rotation is complete, the height of the subtree whose parent was the root decreases, and it is balanced and does not need to be updated up.

5. Performance of AVL trees

AVL tree is an absolutely balanced binary search tree, which requires that the absolute height difference between left and right subtrees of each node not exceed 1, thus ensuring efficient time complexity in querying. However, if you want to make some structural modifications to the AVL tree, the performance is very poor, such as maintaining its absolute balance when inserting, rotating more often, or even worse, deleting, may keep the rotation to the root position.

Therefore, if you need an efficient and ordered data structure for querying, and the number of data is static (that is, unchanged), you can consider AVL trees, but a structure that is often modified is not appropriate.

Complete Code

#pragma once
#include <iostream>
#include <assert.h>

using namespace std;

template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;

	int _bf; //Balance factor

	pair<K, V> _kv;		//Packaging two data into one using a structure template

	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		,_parent(nullptr)
		,_bf(0)
		,_kv(kv)
	{}
};


template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
private:
	Node* _root;

	void _Destory(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_Destory(root->_left);
		_Destory(root->_right);

		delete root;
	}

	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* parentP = parent->_parent;

		if (subLR)							//The right subtree of the left subtree connects to the right of the parent
			subLR->_parent = parent;		
		
		parent->_left = subLR;
		subL->_right = parent;
		parent->_parent = subL;

		// If parent is the root node, the root new pointer to the root node
		if (parent == _root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			// If a parent is a subtree, it may be the left subtree of its parent or the right subtree
			if (parentP->_left == parent)
				parentP->_left = subL;
			else
				parentP->_right = subL;

			subL->_parent = parentP;
		}

		// Update the balance factor of some nodes according to the adjusted structure
		subL->_bf = parent->_bf = 0;
	}

	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* parentP = parent->_parent;

		if (subRL)
			subRL->_parent = parent;

		parent->_right = subRL;
		subR->_left = parent;
		parent->_parent = subR;

		// If parent is the root node, the root new pointer to the root node
		if (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			// If a parent is a subtree, it may be the left subtree of its parent or the right subtree
			if (parentP->_left == parent)
				parentP->_left = subR;
			else
				parentP->_right = subR;

			subR->_parent = parentP;
		}

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

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

		RotateL(parent->_left);
		RotateR(parent);

		if (bf == -1)				//Situation 1:
		{
			subL->_bf = 0;
			parent->_bf = 1;
			subLR->_bf = 0;
		}
		else if (bf == 1)				//Scenario 2:
		{
			parent->_bf = 0;
			subL->_bf = -1;
			subLR->_bf = 0;
		}
		else if (bf == 0)			//Situation three:
		{
			parent->_bf = 0;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;	//Before rotating, save the balance factor of SubLR, and after rotating, adjust the balance factor of other nodes according to this balance factor

		RotateR(parent->_right);		//First Right
		RotateL(parent);

		//Update Balance Factor
		if (bf == 1)				//Situation 1:
		{
			subR->_bf = 0;
			parent->_bf = -1;
			subRL->_bf = 0;
		}
		else if (bf == -1)				//Scenario 2:
		{
			parent->_bf = 0;
			subR->_bf = 1;
			subRL->_bf = 0;
		}
		else if (bf == 0)			//Situation three:
		{
			parent->_bf = 0;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

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

	int _Height(Node* root)
	{
		if (root == nullptr)
		{
			return 0;
		}

		int left = _Height(root->_left);
		int right = _Height(root->_right);

		return right > left ? right + 1 : left + 1;
	}

	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
		{
			return true;
		}

		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);

		// Check that the balance factor is correct
		if (rightHeight - leftHeight != root->_bf)
		{
			cout << "Abnormal balance factor:" << root->_kv.first << endl;
			return false;
		}

		return abs(rightHeight - leftHeight) < 2
			&& _IsBalance(root->_left)
			&& _IsBalance(root->_right);
	}

public:
	AVLTree()
		:_root(nullptr)
	{}

	~AVLTree()
	{
		_Destory(_root);

		_root = nullptr;
	}

	V& operator[](const K& key)
	{
		pair<Node*, bool> ret = Insert(make_pair(key, V()));
		return ret.first->_kv.second;
	}

	pair<Node*, bool> Insert(const pair<K, V> kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);

			return make_pair(_root, true);
		}


		//1. Find the target location and insert a new node
		Node* parent = _root,* cur = _root;
		while (cur)
		{
			if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return make_pair(cur, true);
			}
		}

		cur = new Node(kv);
		Node* newnode = cur;
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}

		//Control balance
		//1. Update Balance Factor
		//2. Unbalanced rotates
		while (cur != _root)
		{
			if (parent->_left == cur) {			//Handle
				parent->_bf--;	
			}
			else {
				parent->_bf++;
			}

			if (parent->_bf == 0) {
				break;						//Originally = -1/1, and added to the empty space on the other side, height does not change
			}
			else if(parent->_bf == 1 || parent->_bf == -1)
			{
				//Height changes, need to continue changing parent node
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				//Need to rotate
				if (parent->_bf == -2)			//Left High
				{
					if (parent->_bf == -1)
					{
						//Left High, Right Single Rotation
						RotateR(parent);
					}
					else
					{
						RotateLR(parent);
					}
				}
				else
				{
					if (parent->_bf == 1)
					{
						//Left High, Right Single Rotation
						RotateL(parent);
					}
					else
					{
						RotateRL(parent);
					}
				}
			}
			else
			{
				assert(false);
			}
		}

		return make_pair(newnode, true);
	}

	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < key)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}

		return nullptr;
	}

	void InOrder()
	{
		_InOrder(_root);
	}

	bool IsAVLTree()
	{
		return _IsBalance(_root);
	}

	bool Erase(const K& key)
	{
		return false;
	}
};

test.cpp

#include"AVLTree.h"
#include<string>

void TestAVLTree()
{
	//int a[] = { 1, 3, 5, 7, 6 };
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	AVLTree<int, int> t;
	for (auto e : a)
	{
		t.Insert(make_pair(e, e));
		cout << "" << e << "->" << t.IsAVLTree() << endl;
	}

	t.InOrder();
	cout << t.IsAVLTree() << endl;
	t.InOrder();
	t[3] *= 10;
	t[4] *= 10;
	t[5] *= 10;
	t.InOrder();

	AVLTree<string, string> dict;
	dict.Insert(make_pair("sort", ""));
	dict.Insert(make_pair("left", ""));
	dict.InOrder();

	dict["left"] = "+";
	dict["string"] = "ַ";
	dict.InOrder();
}

int main()
{
	TestAVLTree();
    printf("This is for Grandma! May all be well with her in heaven");    
    return 0;
}

Result:

 

Tags: C++ data structure STL avl

Posted on Sun, 21 Nov 2021 16:40:12 -0500 by tina88