AVL tree and red black tree

Posted by ghostrider1 on Tue, 08 Feb 2022 22:28:37 +0100

AVL tree

concept

  • Background: Although the binary search tree can shorten the search efficiency, if the data is orderly or close to orderly, the binary search tree will degenerate into a single tree, and the search elements are equivalent to searching elements in the sequence table, which is inefficient, Therefore, two Russian mathematicians G.M. Adelson velskii and E.M.Landis invented a method to solve the above problem in 1962: when inserting new nodes into the binary search tree, if the absolute value of the difference between the left and right subtree heights of each node can not exceed 1 (the nodes in the tree need to be adjusted), the height of the tree can be reduced, so as to reduce the average search length;
  • nature:
    • Its left and right subtrees are AVL trees;
    • The absolute value of the difference between the height of the left and right subtrees (referred to as the balance factor) shall not exceed 1 (specifically: - 1 / 0 / 1);
  • Adjustment: if the subtree with pParent as the root is unbalanced, that is, the balance factor of pParent is 2 or - 2, consider the following situations:
    1. The balance factor of pParent is 2, which indicates the height of the right subtree of pParent. Set the root of the right subtree of pParent as pSubR
      • When the balance factor of pSubR is 1, execute left single rotation;
      • When the balance factor of pSubR is - 1, execute right and left double rotation;
    2. The balance factor of pParent is - 2, which indicates the height of the left subtree of pParent. Set the root of the left subtree of pParent as pSubL
      • When the balance factor of pSubL is - 1, execute right single rotation;
      • When the balance factor of pSubL is 1, execute left-right double rotation;
    • After the rotation is completed, the height of the subtree with the original pParent as the root is reduced, which has been balanced and does not need to be updated upward;
  • Summary: 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 should not exceed 1, which can ensure the efficient time complexity of query, that is, log2N. However, if you want to modify the structure of AVL tree, the performance is very low. For example, when inserting, you need to maintain its absolute balance, and there are many times of rotation. Worse, when deleting, you may have to keep the rotation to the root position;
    Therefore: if you need an efficient and orderly data structure for query, and the number of data is static (that is, it will not change), you can consider AVL tree, but a structure is often modified, which is not suitable;

realization

#include<iostream>
using namespace std;

template<class T>
struct AVLNode {
	//data
	T _data;
	//Balance factor
	int _bf;
	//Left child pointer
	AVLNode* _left;
	//Right child pointer
	AVLNode* _right;
	//Parent node pointer
	AVLNode* _parent;
	//Constructor with parameters
	AVLNode(const T& val = T())
		:_parent(nullptr)
		,_left(nullptr)
		,_right(nullptr)
		,_data(val)
		,_bf(0)
	{}
};

template<class T>
class AVLTree {
public:
	typedef AVLNode<T> Node;
	//Constructor
	AVLTree()
		:_root(nullptr)
	{}
	//Left hand operation
	void RotateL(Node* parent) {
		//First get the right child to rotate the node
		Node* node = parent->_right;
		//Then get the left node of the right child who wants to rotate the node
		Node* nodeleft = node->_left;
		//Re link
		parent->_right = nodeleft;
		if(nodeleft)
			nodeleft->_parent = parent;
		//If the node to be rotated is the root node, additional consideration is required
		if (parent == _root) {
			_root = node;
			node->_parent = nullptr;
		}
		//If it is not the root node, it will be adjusted normally
		else {
			Node* tmp = parent->_parent;
			node->_parent = tmp;
			if (tmp->_left == parent)
				tmp->_left = node;
			else
				tmp->_right = node;
		}
		parent->_parent = node;
		node->_left = parent;
		//Adjust balance factor
		parent->_bf = node->_bf = 0;
	}
	//Right hand operation
	void RotateR(Node* parent) {
		//First get the left child who wants to rotate the node
		Node* node = parent->_left;
		//Then get the right node of the left child who wants to rotate the node
		Node* noderight = node->_right;
		//Then adjust the link
		parent->_left = noderight;
		if (noderight)
			noderight->_parent = parent;
		//If the node to be rotated is the root node, additional consideration is required
		if (parent == _root) {
			_root = node;
			node->_parent = nullptr;
		}
		//If not, adjust normally
		else {
			Node* tmp = parent->_parent;
			node->_parent = tmp;
			if (tmp->_left = parent)
				tmp->_left = node;
			else
				tmp->_right = node;
		}
		parent->_parent = node;
		node->_right = parent;
		//Adjust balance factor
		parent->_bf = node->_bf = 0;
	}
	//Data insertion
	bool insert(const T& val) {
		//If the current tree is empty, the node will be inserted directly
		if (_root == nullptr) {
			_root = new Node(val);
			return true;
		}
		//Otherwise, first find a suitable location (the same as the binary search tree), and then insert
		Node* parent = nullptr;
		Node* node = _root;
		while (node) {
			parent = node;
			if (node->_data == val)
				return false;
			else if (node->_data > val)
				node = node->_left;
			else
				node = node->_right;
		}
		//After finding the appropriate location, insert the node and link the pointer
		node = new Node(val);
		if (parent->_data > val)
			parent->_left = node;
		else
			parent->_right = node;
		node->_parent = parent;
		//Adjust the balance factor after inserting the node
		while (parent) {
			//Update the balance factor of the parent node
			if (parent->_left == node)
				parent->_bf--;
			else
				parent->_bf++;
			//Check the change of balance factor
			if (parent->_bf == 0)
				//If it changes to 0, it means no effect
				break;
			else if (parent->_bf == -1 || parent->_bf == 1) {
				//Continue to check the balance factor upward
				node = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == -2 || parent->_bf == 2) {
				if (parent->_bf == -2 && node->_bf == -1)
					//Right hand adjustment is required at this time
					RotateR(parent);
				else if(parent->_bf == 2 && node->_bf == 1)
					//Left hand rotation adjustment is required at this time
					RotateL(parent);
				else if (parent->_bf == -2 && node->_bf == 1) {
					//First save the balance factor of the right subtree of the current node
					int bf = node->_right->_bf;
					//Rotate the current node to the left first, and then rotate the parent node to the right
					RotateL(node);
					RotateR(node);
					//Modified balance factor
					if (bf == -1) {
						parent->_bf = 1;
						node->_bf = 0;
					}
					else if (bf == 1) {
						parent->_bf = 0;
						node->_bf = -1;
					}
				}
				else if (parent->_bf == 2 && node->_bf == -1) {
					//First save the balance factor of the left subtree of the current node
					int bf = node->_left->_bf;
					//Rotate the current node to the right, and then rotate the parent node to the left
					RotateR(node);
					RotateL(node);
					//Modified balance factor
					if (bf == -1) {
						parent->_bf = 0;
						node->_bf = 1;
					}
					else if(bf == 1) {
						parent->_bf = -1;
						node->_bf = 0;
					}
				}
				//After adjustment, end the cycle
				break;
			}
		}
		return true;
	}
	//Middle order traversal
	void inorder() {
		_inorder(_root);
		cout << endl;
	}
	//Check whether the height difference between the left and right subtrees is the same as the balance factor
	bool isBalance(Node* root) {
		//Returns true if the tree is empty
		if (root == nullptr)
			return true;
		//Get the height of the left and right subtrees
		int left = High(root->_left);
		int right = High(root->right);
		//Judge whether the height difference between the left and right subtrees is equal to the balance factor
		if (right - left == root->_bf)
			return false;
		//Returns whether the balance factor of the current node is less than 2 and whether the left and right subtrees meet the requirements
		return abs(root->_bf) < 2 && isBalance(root->left) && isBalance(root->right);
	}
	//Gets the height of the tree
	int High(Node* root) {
		if (root == nullptr)
			return 0;
		//The height of the tree is the higher of the left and right subtrees
		int left = High(root->_left);
		int right = High(root->_right);
		return left > right ? left + 1 : right + 1;
	}

	//Delete the node: first delete the node according to the two fork search tree to delete the node and adjust the balance factor after deletion.
		//After deletion, the balance factor of the parent node changes to - 1 or 1, indicating that adjustment is not required
		//When deleting, the balance factor of the parent node becomes 0, so you need to continue to adjust upward until you encounter the first - 2 or 2, and then start to rotate and adjust
private:
	//Middle order traversal of binary tree
	void _inorder(Node* root) {
		if (root == nullptr)
			return;
		_inorder(root->_left);
		cout << root->_data << " ";
		_inorder(root->_right);
	}
	Node* _root;
};

int main() {

	return 0;
}

Red black tree

concept

  • Concept: Red Black tree is a binary search tree, but a storage bit is added on each node to represent the color of the node, which can be Red or Black. By limiting the coloring mode of each node on any path from root to leaf, the Red Black tree ensures that no path will be twice longer than other paths, so it is close to balance;
  • nature:
    1. When each node is inserted, it is red by default. Finally, it needs to be adjusted. After adjustment, it is either red or black;
    2. The root node is black;
    3. If a node is red, its two child nodes are black, that is, continuous red nodes are not allowed;
    4. For each node, the simple path from the node to all its descendant leaf nodes contains the same number of black nodes;
    5. Each leaf node is black (the leaf node here refers to the empty node);
  • Structure: the red black tree contains a head node, which does not contain any data. Its parent pointer points to the root node, the left pointer points to the leftmost node of the tree, and a pointer points to the rightmost node of the tree;

realization

  • Note: since the containers such as map and set are implemented by the red black tree, in the process of implementing the red black tree, the structure is designed to facilitate the implementation of the underlying structure of map and set. Many details and key points are written in the comments of the code, which can be viewed carefully;
#include<iostream>
using namespace std;

enum COLOR {
	BLACK,
	RED
};

//Red black tree node
template<class V>
struct RBNode {
	//Pointer to parent node
	RBNode* _parent;
	//Pointer to left child node
	RBNode* _left;
	//Pointer to the right child node
	RBNode* _right;
	//The color of the node
	COLOR _color;
	//data
	V _val;
	//Constructor of node
	RBNode(const V& val = V())
		:_parent(nullptr)
		,_left(nullptr)
		,_right(nullptr)
		,_color(RED)
		,_val(val)
	{}
};

//Iterator encapsulating red black tree
template<class V>
struct RBTreeIterator {
	typedef RBNode<V> Node;
	typedef RBTreeIterator<V> Self;
	//Member variable: red black tree node
	Node* _node;
	//Constructor
	RBTreeIterator(Node* node)
		:_node(node)
	{}
	//Overload * operator
	V& operator*() {
		return _node->_val;
	}
	//Overload - > operator
	V* operator->() {
		return &(_node->_val);
	}
	//Overload= operator
	bool operator!=(const Self& it) {
		return _node != it._node;
	}
	//Overloaded leading + + operator
	Self& operator++() {
		//If the node of the current iterator has a right subtree, the iterator updates to the leftmost node of the right subtree
		if (_node->_right) {
			_node = _node->_right;
			while (_node->_left)
				_node = _node->_left;
		}
		//If there is no right subtree, there are two cases
		//If the current node is the left child of the parent node, the iterator is directly updated to the parent node location
		//If the current node is the right child of the parent node, update the current node to the location of the parent node and the parent node to the location of the grandfather node, and continue the circular judgment
		else {
			//Get the parent node of the current node
			Node* parent = _node->_parent;
			//Cycle to determine whether it is on the right
			while (parent->_right = _node) {
				_node = parent;
				parent = _node->_parent;
			}
			//Avoid the situation that the tree has no right subtree, because the end condition of the iterator is to reach the head node
			//If the tree has no right subtree, it will loop between the root node and the head node
			if (_node->_right != parent)
				_node = parent;
		}
		return *this;
	}
	Self& operator--() {
		//If the node of the current iterator has a left subtree, the iterator updates to the rightmost node of the left subtree
		if (_node->_left) {
			_node = _node->_left;
			while (_node->_right)
				_node = _node->_right;
		}
		//If there is no left subtree, there are two cases
		//If the current node is the right child of the parent node, the iterator is directly updated to the parent node location
		//If the current node is the left child of the parent node, update the current node to the location of the parent node and the parent node to the location of the grandfather node, and continue the circular judgment
		else {
			//Get the parent node
			Node* parent = _node->_parent;
			//Circular judgment
			while (parent->_left == _node) {
				_node = parent;
				parent = _node->_parent;
			}
			//Avoid the case that the tree has no left subtree, because the end condition of the iterator is to reach the head node
			//If the tree has no left subtree, it will loop between the root node and the head node
			if (_node->_left != parent)
				_node = parent;
		}
		return *this;
	}
};

//Red black tree
template<class K, class V, class keyofval>
class RBTree {
public:
	typedef RBNode<V> Node;
	typedef RBTreeIterator<V> iterator;
	//Constructor
	RBTree()
		//Create an empty header node
		:_header(new Node)
	{
		_header->_left = _header->_right = _header;
	}
	//iterator 
	iterator begin() {
		return iterator(_header->_left);
	}
	iterator end() {
		return iterator(_header);
	}
	iterator rbegin() {
		return iterator(_header->_right);
	}
	iterator rend() {
		return iterator(_header);
	}
	//Insert operation
	pair<iterator, bool> insert(const V& val) {
		//If it is an empty red black tree, insert the data directly
		if (_header->_parent == nullptr) {
			//Insert data directly
			Node* root = new Node(val);
			//Establish connection
			root->_parent = _header;
			_header->_parent = _header->_left = _header->_right = root;
			root->_color = BLACK;
			return make_pair(iterator(_header->_parent), true);
		}
		//Imitation function: used to obtain the comparison data required for storing data - key, which is provided by the upper layer (map/set, etc.)
		keyofval kov;
		//Start searching for data to be inserted
		Node* parent = nullptr;
		Node* cur = _header->_parent;
		while (cur) {
			parent = cur;
			if (kov(cur->_val) == kov(val))
				return make_pair(iterator(cur), false);
			else if (kov(cur->_val) > kov(val))
				cur = cur->_left;
			else
				cur = cur->_right;
		}
		//After finding the location, start inserting and establish a link
		cur = new Node(val);
		if (kov(parent->_val) > kov(cur))
			parent->_left = cur;
		else
			parent->_right = cur;
		cur->_parent = parent;
		Node* tmp = cur;
		//Judge up whether the colors of the current node and the parent node match
		while (cur->_color == RED && cur->_parent->_color == RED) {
			parent = cur->_parent;
			//Get the grandfather node first
			Node* gfather = parent->_parent;
			//It is discussed by case. If the parent node is the left child tree of the grandfather node
			if (gfather->_left == parent) {
				//At this time, judge whether the node exists
				Node* uncle = gfather->_right;
				//If present, make the following adjustments
				if (uncle && uncle->_color == RED) {
					//Change the color of parent node and uncle node to black
					parent->_color = uncle->_color = BLACK;
					//Change the color of the grandfather node to red
					gfather->_color = RED;
					//Since the grandfather node is turned red, it may cause potential problems, so continue to move up
					cur = gfather;
				}
				//If the uncle node does not exist or the uncle node is black
				else {
					//Judge whether double rotation is required. Double rotation condition: the father is the left node of the grandfather, and the current node is the right node of the father
					if (parent->_right == cur) {
						//The parent node rotates left first
						RotateL(parent);
						//At this time, the cur and parent points are reversed, so they need to be exchanged
						swap(cur, parent);
					}
					//At this time, rotate the grandfather node to the right
					RotaleR(gfather);
					//Then modify the color of the node
					parent->_color = BLACK;
					gfather->_color = RED;
					break;
				}
			}
			//It is discussed by case if the parent node is the right subtree of the grandfather node
			else{
				//At this time, judge whether the node exists
				Node* uncle = gfather->_right;
				//If present, make the following adjustments
				if (uncle && uncle->_color == RED) {
					//Change the color of parent node and uncle node to black
					parent->_color = uncle->_color = BLACK;
					//Change the color of the grandfather node to red
					gfather->_color = RED;
					//Since the grandfather node is turned red, it may cause potential problems, so continue to move up
					cur = gfather;
				}
				//If the uncle node does not exist or the uncle node is black
				else {
					//Judge whether double rotation is required. Double rotation condition: the father is the right node of the grandfather, and the current node is the left node of the father
					if (parent->_left == cur) {
						//The parent node rotates right first
						RotateR(parent);
						//At this time, the cur and parent points are reversed, so they need to be exchanged
						swap(cur, parent);
					}
					//At this time, rotate the grandfather node to the left
					RotaleL(gfather);
					//Then modify the color of the node
					parent->_color = BLACK;
					gfather->_color = RED;
					break;
				}
			}
		}
		//After the adjustment is completed, the color of the root node may be changed during the adjustment, so just change back to black
		_header->_parent->_color = BLACK;
		//And modify the direction of the left and right pointers of the header node
		_header->_left = LeftMost();
		_header->_right = RightMost();
		return make_pair(iterator(tmp), true);
	}
	//Check whether the red black tree meets the left and right properties of red black tree
	bool isBalance() {
		//If it is an empty tree, it is a red black tree
		if (_header->_parent == nullptr)
			return true;
		//If it is red, it is not a red black tree
		if (_header->_parent->_color == RED)
			return false;
		//Next, judge whether there is continuous red and the number of black nodes in each path
		int count = 0, num = 0;
		Node* cur = _header->_parent;
		while (cur) {
			if (cur->_color == BLACK)
				num++;
			cur = cur->_left;
		}
		return _isBalance(_header->_parent, count, num);
	}
	//Middle order traversal
	void inorder() {
		_inorder(_header->_parent);
		cout << endl;
	}
private:
	//Left hand operation
	void RotateL(Node* cur) {
		//Gets the right curR of the node to rotate
		Node* curR = cur->_right;
		//Get left child curl of right child curl
		Node* curRL = curR->_left;
		//Start establishing pointer connection
		cur->_right = curRL;
		curR->_left = cur;
		if (curRL)
			curRL->_parent = cur;
		//If the node to be rotated is the root node, special treatment is required
		if (cur == _header->_parent) {
			_header->_parent = curR;
			curR->_parent = _header;
		}
		//If it is not the root node, it will be handled normally
		else {
			Node* node = cur->_parent;
			curR->_parent = node;
			if (node->_left == cur)
				node->_left = curR;
			else
				node->_right = curR;
		}
		cur->_parent = curR;
	}
	//Right hand operation
	void RotateR(Node* cur) {
		//Gets the left curL of the node to rotate
		Node* curL = cur->_left;
		//Get the right child curLR of the left child curL
		Node* curLR = curL->_right;
		//Start establishing pointer connection
		cur->_left = curLR;
		curL->_right = cur;
		if (curLR)
			curLR->_parent = cur;
		//If the node to be rotated is the root node, special treatment is required
		if (cur == _header->_parent) {
			_header->_parent = curL;
			curL->_parent = _header;
		}
		//If it is not the root node, it will be handled normally
		else {
			Node* node = cur->_parent;
			curL->_parent = node;
			if (node->_left == cur)
				node->_left = curL;
			else
				node->_right = curL;
		}
		cur->_parent = curL;
	}
	//Get leftmost node
	Node* LeftMost() {
		Node* cur = _header->_parent;
		while (cur && cur->_left)
			cur = cur->_left;
		return cur;
	}
	//Get rightmost node
	Node* RightMost() {
		Node* cur = _header->_parent;
		while (cur && cur->_right)
			cur = cur->_right;
		return cur;
	}
	//Recursively judge whether it is a red black tree
	bool _isBalance(Node* node, int count, int num) {
		//When walking to the empty node, judge whether the number of black nodes is the same as the determined number
		if (node == nullptr)
			return count == num;
		//Judge whether there are continuous red nodes
		if (node->_parent && node->_color == RED && node->_parent->_color == RED)
			return false;
		//Count the number of black nodes on a path
		if (node->_color == BLACK)
			count++;
		//At the same time, judge whether the left and right subtrees are also satisfied
		return _isBalance(node->_left, count, num)
			&& _isBalance(node->_right, count, num);
	}
	//Middle order traversal
	void _inorder(Node* root) {
		if (root) {
			_inorder(root->_left);
			cout << root->_val << ' ';
			_inorder(root->_right);
		}
	}
	Node* _header;
};


//Using red black tree to simply implement map
template<class K, class V>
class Map {
	struct keyofval {
		const K& operator()(const pair<K, V>& val) {
			return val.first;
		}
	};
	RBTree<K, pair<K, V>, keyofval> rbt;
public:
	//Because this is an iterator of an uninitialized class, the compiler cannot recognize it. Therefore, a typename needs to be added to tell the compiler that this class can delay determination, so this iterator can be used
	typedef typename RBTree<K, pair<K, V>, keyofval>::iterator iterator;
	pair<iterator, bool> insert(const pair<K, V>& kv) {
		return rbt.insert(kv);
	}
	//Iterator for Map
	iterator begin() {
		return rbt.begin();
	}
	iterator end() {
		return rbt.end();
	}
	iterator rbegin() {
		return rbt.rbegin();
	}
	iterator rend() {
		rbt.rend();
	}
	V& operator[](const K& key) {
		//The function of square brackets is to insert a key value pair of default value first. Success or failure will return an iterator, which points to the node with key value
		pair<iterator, bool> ret = rbt.insert(make_pair(key, V()));
		return ret.first->second;
	}
};

template<class V>
class Set {
	struct keyofval {
		const V& operator()(const V& key) {
			return key;
		}
	};
	RBTree<V, V, keyofval> rbt;
public:
	typedef typename RBTree<V, V, keyofval>::iterator iterator;
	pair<iterator, bool> insert(const V& val) {
		return rbt.insert(val);
	}
};

int main() {
	
	return 0;
}

Topics: Algorithm data structure