An article takes you to understand red-black trees and simulate them

Insert image description here

The concept and properties of red-black trees

1. Concept

A red-black tree is a binary search tree , but a storage bit is added to each node to represent the color of the node, which can be Redor Black. By restricting the coloring of each node on any path from the root to the leaf, the red-black tree ensures that no path is twice as long as any other path, and is therefore close to balance.

Insert image description here

2. Nature

  1. Each node is either red or black.
  2. The root node is black.
  3. If a node is red, then both of its child nodes are black.
  4. Every path from any node to each of its leaf nodes contains the same number of black nodes, which is called "black-height equality."
  5. Each leaf node (NIL node, usually represented as an empty node) is black.

These properties of the red-black tree ensure the balance of the tree, thereby ensuring that the height of the tree is within the logarithmic range, so that the time complexity of basic operations remains within the O(log n)level

The structure of a red-black tree

In order to simplify the subsequent implementation of associative containers, a head node is added to the implementation of the red-black tree. Because the following node must be black, in order to distinguish it from the root node, the head node is colored black and the domain of the head node points pParentto The root node of the red-black tree, pLeftthe domain points to the smallest node in the red-black tree, and _pRightthe domain points to the largest node in the red-black tree.

When adding a head node to the implementation of a red-black tree, the purpose is to simplify operations and handle edge cases without having to write separate code for special cases. This head node is usually a black node located above the root node of the red-black tree. It pParentpoints to the root node of the red-black tree, pLeftpoints to the smallest node in the red-black tree, and pRightpoints to the largest node in the red-black tree.

The following is an explanation of the role and implementation details of the head node:

  1. Simplified boundary condition processing : The existence of the head node makes the root node always black, because the root node is the right child node of the head node. In this way, in operations such as insertion and deletion, there is no need to deal with the color of the root node and the existence of the parent node in special cases.
  2. Quickly access the smallest and largest nodes : the head node pLeftpoints to the smallest node in the red-black tree and pRightpoints to the largest node. This means you can find the smallest and largest nodes in the tree in O(1) time without having to traverse.
  3. Unified root node access : Whether it is insertion, deletion or other operations, you can always start the operation from the head node, because the head node points pParentto the real root node. This simplifies the code logic because you don't have to handle the root node case specially.

The following is an example implementation of a header node:

struct Node {
    
    
    int data;
    Color color;
    Node* left;
    Node* right;
    Node* parent;
};

class RedBlackTree {
    
    
private:
    Node* root; // 实际的根节点
    Node* header; // 头结点

    // ...其他成员函数和辅助函数...

public:
    RedBlackTree() : root(nullptr), header(new Node) {
    
    
        header->color = BLACK;
        header->left = nullptr;
        header->right = nullptr;
        header->parent = nullptr;
    }

    // ...其他公共成员函数...
};

In this example, we added a headermember variable named which is a pointer to the head node. The initialization of the head node is completed in the constructor and ensures that it is always black, and the root node is located in the center of the head node pParent. The existence of the head node will simplify the operation and edge case processing of red-black trees.

This is an implementation method, but we will not implement it in this way later. Of course, you can also choose this method to implement it, which is simpler. The implementation process of the red-black tree is just to make us more familiar with its underlying reality. There are very few scenarios that we need to implement .

Node definition of red-black tree and member definition of red-black tree structure

Here our implementation adopts the form of a three-pronged chain. We will mention the benefits of this writing method later.

We use key-value pair templates here to facilitate later simulation implementation of map and set.

enum Colour
{
    
    
	RED,
	BLACK
};

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

	pair<K, V> _kv;
	Colour _col;

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

template<class K, class V>
struct RBTree
{
    
    
	typedef RBTreeNode<K,V> Node;
private:
	Node* _root = nullptr;
};
  1. Enumeration typeColour : An enumeration type is defined here, including two members REDand BLACK. This enumeration type is used to represent the color of nodes in a red-black tree. In a red-black tree, nodes can be red or black, and it is a common practice to use this enumeration type to represent the node color.

  2. StructureRBTreeNode<K, V> : This structure defines the node structure of the red-black tree, which contains the following member variables:

    • _leftand _right: pointers to the left and right child nodes of the node respectively.
    • _parent: Pointer to the node's parent node.
    • _kv: Member variable used to store key-value pairs. This key-value pair is usually used to store the node's data.
    • _col: Indicates the color of the node, which can be REDor BLACK.

    The constructor RBTreeNode(const pair<K, V>& kv)is used to initialize the node object and assign initial values ​​to each member variable, including key-value pairs _kvand colors _col.

  3. StructureRBTree<K, V> : This structure defines the structure of the entire red-black tree, which contains the following member variables and an alias:

    • _root: Pointer to the root node of the red-black tree. The root node is the starting point of the red-black tree, and all operations start from the root node.

    typedef RBTreeNode<K,V> Node;An alias is defined Nodeto represent the type of red-black tree node. Doing this simplifies your code and makes it easier to reference node types in your code.

Insertion of red-black trees

1. Insert new nodes according to the tree rules of binary search

bool Insert(const pair<K,V>& kv)
{
    
    
    if (_root == nullptr)//为空直接插入
    {
    
    
        _root = new Node(kv);
        _root->_col = BLACK;
        return true;
    }

    Node* parent = nullptr;
    Node* cur = _root;
    while (cur)//同搜索树规则找到插入位置
    {
    
    
        if (cur->_kv.first < kv.first)
        {
    
    
            parent = cur;
            cur = cur->_right;
        }
        else if (cur->_kv.first > kv.first)
        {
    
    
            parent = cur;
            cur = cur->_left;
        }
        else
        {
    
    
            return false;
        }
    }

    cur = new Node(kv);
    cur->_col = RED;//新插入节点即为红节点,不懂结合性质和我下面的讲解

    if (parent->_kv.first < kv.first)//同搜索树规则先直接插入
    {
    
    
        parent->_right = cur;
    }
    else
    {
    
    
        parent->_left = cur;
    }

    cur->_parent = parent;//更新新插入节点的父指针

    while (parent && parent->_col == RED)
    {
    
    
        Node* grandfater = parent->_parent;
        assert(grandfater);
        assert(grandfater->_col == BLACK);
        if (parent == grandfater->_left)//具体看下面讲解
        {
    
    
            Node* uncle = grandfater->_right;
            // 情况一 : uncle存在且为红,变色+继续往上处理
            if (uncle && uncle->_col == RED)
            {
    
    
                parent->_col = uncle->_col = BLACK;
                grandfater->_col = RED;
                // 继续往上处理
                cur = grandfater;
                parent = cur->_parent;
            }// 情况二+三:uncle不存在 + 存在且为黑
            else
            {
    
    
                // 情况二:右单旋+变色
                if (cur == parent->_left)
                {
    
    
                    RotateR(grandfater);
                    parent->_col = BLACK;
                    grandfater->_col = RED;
                }
                else
                {
    
    
                    // 情况三:左右单旋+变色
                    RotateL(parent);
                    RotateR(grandfater);
                    cur->_col = BLACK;
                    grandfater->_col = RED;
                }

                break;
            }
        }
        else // (parent == grandfater->_right)
        {
    
    
            Node* uncle = grandfater->_left;
            // 情况一
            if (uncle && uncle->_col == RED)
            {
    
    
                parent->_col = uncle->_col = BLACK;
                grandfater->_col = RED;
                // 继续往上处理
                cur = grandfater;
                parent = cur->_parent;
            }
            else
            {
    
    
                // 情况二:左单旋+变色
                if (cur == parent->_right)
                {
    
    
                    RotateL(grandfater);
                    parent->_col = BLACK;
                    grandfater->_col = RED;
                }
                else
                {
    
    
                    // 情况三:右左单旋+变色
                    RotateR(parent);
                    RotateL(grandfater);
                    cur->_col = BLACK;
                    grandfater->_col = RED;
                }

                break;
            }
        }

    }

    _root->_col = BLACK;
    return true;
}

In fact, the previous code is similar to the insertion of the AVL tree we talked about in the previous article, except that the red and black trees here have no balance factor and become colors.

Next we look at the analysis of each situation

2. Check whether the properties of the red-black tree cause damage after the new node is inserted.

Because the default color of a new node is red, therefore: if the color of its parent node is black, it does not violate any properties of the red-black tree, and no adjustment is needed; but when the color of the parent node of the newly inserted node is red, it violates the properties Three cannot have red nodes connected together. At this time, we need to discuss the situation of red and black trees:

Convention: cur is the current node, p is the parent node, g is the grandparent node, and u is the uncle node.

Case 1: cur is red, p is red, g is black, u exists and is red

Insert image description here

Cur and p are both red. Solution: Change p and u to black, g to red, then treat g as cur, and continue to adjust upward.

Case 2: cur is red, p is red, g is black, u does not exist/u exists and is black

Insert image description here

If p is the left child of g and cur is the left child of p, a right single rotation will be performed. On the contrary, if p is the right child of g and cur is the right child of p, a left single rotation will be performed. p and g will change color – p will turn black. , g turns red

Case 3: cur is red, p is red, g is black, u does not exist/u exists and is black

Insert image description here

p is the left child of g, and cur is the right child of p, then perform a left single rotation on p; on the contrary, if p is the right child of g, cur is the left child of p, then perform a right single rotation on p, then it is converted to Case 2

3. Insert code analysis

  1. If _rootis empty, indicating that the red-black tree is empty, a new node will be created _rootand its color will be set to black. This is the root node, and following the red-black tree rules, the root node must be black.
  2. If _rootis not empty, the code looks for the correct location in the tree to insert the new node. By traversing the tree, find the correct parent node parentand the location where the new node should be inserted cur.
  3. Returns if the key to be inserted already exists in the tree, falsesince duplicate keys are not allowed.
  4. Create a new node curand set its color to red. The color of the new node is initially set to red to satisfy the nature of a red-black tree.
  5. Then, the code enters a loop. The purpose of the loop is to ensure that the properties of a red-black tree are still satisfied after inserting a new node. This is a critical part of the red-black tree insertion operation.
    • In the loop, first check parentthe color of the node. If parentit is red, then further processing is needed to maintain the red-black tree properties.
    • The code then checks unclethe color of the node, which is parenta sibling of . According to unclethe color, it is divided into case one, case two and case three.
    • Finally, after exiting the loop, set the color of the root node to black to ensure that the root node meets the properties of a red-black tree.

4. Insert dynamic effects

1. Build a red-black tree by inserting in ascending order

Insert image description here

2. Build a red-black tree by inserting in descending order

Insert image description here

3. Random insertion to build a red-black tree

Insert image description here

Verification of red-black trees

bool IsBalance()
	{
    
    
		if (_root == nullptr)
		{
    
    
			return true;
		}

		if (_root->_col == RED)//检查根节点是否为黑
		{
    
    
			cout << "根节点不是黑色" << endl;
			return false;
		}

		// 黑色节点数量基准值
		int benchmark = 0;
		return PrevCheck(_root, 0, benchmark);
	}

Implementation of return value function PrevCheck

bool PrevCheck(Node* root, int blackNum, int& benchmark)
{
    
    
    if (root == nullptr)
    {
    
    
        if (benchmark == 0)
        {
    
    
            benchmark = blackNum;
            return true;
        }

        if (blackNum != benchmark)
        {
    
    
            cout << "某条黑色节点的数量不相等" << endl;
            return false;
        }
        else
        {
    
    
            return true;
        }
    }

    if (root->_col == BLACK)
    {
    
    
        ++blackNum;
    }

    if (root->_col == RED && root->_parent->_col == RED)
    {
    
    
        cout << "存在连续的红色节点" << endl;
        return false;
    }

    return PrevCheck(root->_left, blackNum, benchmark)
        && PrevCheck(root->_right, blackNum, benchmark);
}
  1. Black height sameness check : The function uses recursion to traverse the nodes in the tree and maintains a blackNumvariable to track the number of black nodes passed from the root node to the current node. During the traversal, it checks whether the number of black nodes on each path from the root to the leaf node is the same. If the number of black nodes on the path is different, it means that the properties of the red-black tree are violated.
  2. Continuous red node check : During the traversal process, the code also checks whether there are consecutive red nodes. In a red-black tree, two adjacent red nodes are not allowed to exist. If there are continuous red nodes, it also violates the properties of red-black trees.
  3. Return value : The function returns a Boolean value to indicate whether the red-black tree satisfies the properties. The function will return if any properties are found to be broken during the traversal false, otherwise true.

This function is a common method used to verify red-black trees. It is used to check whether the tree maintains the properties of a red-black tree, including red nodes with the same black height and discontinuous ones. If the function returns true, it means that the tree is a valid red-black tree, otherwise it means that the structure of the tree violates the properties of a red-black tree.

Red-black tree traversal output and left-right rotation

1. Traverse the output

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

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

The traversal here is a method of traversing a binary search tree (BST) in order of node value. Here, the red-black tree is also a BST, so in-order traversal can be used to output the nodes in the tree in key order.

The main logic of the function includes:

  1. If the input rootnode is empty (that is, the tree is empty), it returns directly without performing any operation.
  2. Otherwise, the function does the following recursively:
    • First call _InOrderthe function recursively to traverse the left subtree (left child node).
    • Output the key-value pair of the current node (assume here that the key is an integer and the value is an integer, adjust the output format as needed).
    • Finally, the function is called recursively _InOrderto traverse the right subtree (right child node).

2. Left single rotation

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

    parent->_right = subRL;
    if (subRL)
        subRL->_parent = parent;

    Node* ppNode = parent->_parent;

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

    if (_root == parent)
    {
    
    
        _root = subR;
        subR->_parent = nullptr;
    }
    else
    {
    
    
        if (ppNode->_left == parent)
        {
    
    
            ppNode->_left = subR;
        }
        else
        {
    
    
            ppNode->_right = subR;
        }

        subR->_parent = ppNode;
    }

}
  1. First, save parentthe right child node of node subRand subRthe left child node of node respectively. will become the new root node, and will become the right child of the node.subRLsubRsubRLparent
  2. Next, point parentthe right child node pointer of node while making sure that the parent pointer of node points to . This step is to connect with ._rightsubRLsubRL_parentparentsubRLparent
  3. Save parentthe parent node pointer of the node ppNode. ppNodeThis is to update where the left or right child points to after rotation subR, to ensure that the tree is connected correctly.
  4. Point subRthe left child node pointer _leftof parent, and point parentthe parent node pointer of . This step is to rotate to the position of ._parentsubRparentsubR
  5. Next, deal with the case of the root node. If _rootwas pointing , then should parentnow point to , so update to , and set the parent pointer to ._rootsubR_rootsubRsubRnullptr
  6. If _rootdoes not point to parent, you need to update the left or right child node pointing ppNodeaccording to the situation to ensure that the entire tree is connected correctly.ppNodesubR

Left single rotation and right single rotation are basically the same as our previous AVL tree implementation. If you don’t understand, please read the previous article.

3. Right single rotation

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

    parent->_left = subLR;
    if (subLR)
    {
    
    
        subLR->_parent = parent;
    }

    Node* ppNode = parent->_parent;

    subL->_right = parent;
    parent->_parent = subL;

    if (_root == parent)
    {
    
    
        _root = subL;
        subL->_parent = nullptr;
    }
    else
    {
    
    
        if (ppNode->_left == parent)
        {
    
    
            ppNode->_left = subL;
        }
        else
        {
    
    
            ppNode->_right = subL;
        }

        subL->_parent = ppNode;
    }

}
  1. First, save parentthe left child node of node subLand subLthe right child node of node respectively. will become the new root node, and will become the left child of the node.subLRsubLsubLRparent
  2. Next, point parentthe left child node pointer of node and make sure that the parent pointer of node points to . This step is to connect with ._leftsubLRsubLR_parentparentsubLRparent
  3. Save parentthe parent node pointer of the node ppNode. ppNodeThis is to update where the left or right child points to after rotation subL, to ensure that the tree is connected correctly.
  4. Point subLthe right child node pointer _rightof parent, and point parentthe parent node pointer of . This step is to rotate to the position of ._parentsubLparentsubL
  5. Next, deal with the case of the root node. If _rootwas pointing , then should parentnow point to , so update to , and set the parent pointer to ._rootsubL_rootsubLsubLnullptr
  6. If _rootdoes not point to parent, you need to update the left or right child node pointing ppNodeaccording to the situation to ensure that the entire tree is connected correctly.ppNodesubL

Red-black tree simulation implements all codes

#pragma once
#include<iostream>
#include<assert.h>
#include<time.h>
using namespace std;

enum Colour
{
    
    
	RED,
	BLACK
};

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

	pair<K, V> _kv;
	Colour _col;

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

template<class K, class V>
struct RBTree
{
    
    
	typedef RBTreeNode<K,V> Node;
public:
	bool Insert(const pair<K,V>& kv)
	{
    
    
		if (_root == nullptr)
		{
    
    
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}

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

		cur = new Node(kv);
		cur->_col = RED;

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

		cur->_parent = parent;

		while (parent && parent->_col == RED)
		{
    
    
			Node* grandfater = parent->_parent;
			assert(grandfater);
			assert(grandfater->_col == BLACK);
            
			if (parent == grandfater->_left)
			{
    
    
				Node* uncle = grandfater->_right;
				// 情况一 : uncle存在且为红,变色+继续往上处理
				if (uncle && uncle->_col == RED)
				{
    
    
					parent->_col = uncle->_col = BLACK;
					grandfater->_col = RED;
					// 继续往上处理
					cur = grandfater;
					parent = cur->_parent;
				}// 情况二+三:uncle不存在 + 存在且为黑
				else
				{
    
    
					// 情况二:右单旋+变色
					if (cur == parent->_left)
					{
    
    
						RotateR(grandfater);
						parent->_col = BLACK;
						grandfater->_col = RED;
					}
					else
					{
    
    
						// 情况三:左右单旋+变色
						RotateL(parent);
						RotateR(grandfater);
						cur->_col = BLACK;
						grandfater->_col = RED;
					}

					break;
				}
			}
			else // (parent == grandfater->_right)
			{
    
    
				Node* uncle = grandfater->_left;
				// 情况一
				if (uncle && uncle->_col == RED)
				{
    
    
					parent->_col = uncle->_col = BLACK;
					grandfater->_col = RED;
					// 继续往上处理
					cur = grandfater;
					parent = cur->_parent;
				}
				else
				{
    
    
					// 情况二:左单旋+变色
					if (cur == parent->_right)
					{
    
    
						RotateL(grandfater);
						parent->_col = BLACK;
						grandfater->_col = RED;
					}
					else
					{
    
    
						// 情况三:右左单旋+变色
						RotateR(parent);
						RotateL(grandfater);
						cur->_col = BLACK;
						grandfater->_col = RED;
					}

					break;
				}
			}

		}

		_root->_col = BLACK;
		return true;
	}

	void InOrder()
	{
    
    
		_InOrder(_root);
		cout << endl;
	}

	bool IsBalance()
	{
    
    
		if (_root == nullptr)
		{
    
    
			return true;
		}

		if (_root->_col == RED)
		{
    
    
			cout << "根节点不是黑色" << endl;
			return false;
		}

		// 黑色节点数量基准值
		int benchmark = 0;

		return PrevCheck(_root, 0, benchmark);
	}

private:
	bool PrevCheck(Node* root, int blackNum, int& benchmark)
	{
    
    
		if (root == nullptr)
		{
    
    
			if (benchmark == 0)
			{
    
    
				benchmark = blackNum;
				return true;
			}

			if (blackNum != benchmark)
			{
    
    
				cout << "某条黑色节点的数量不相等" << endl;
				return false;
			}
			else
			{
    
    
				return true;
			}
		}

		if (root->_col == BLACK)
		{
    
    
			++blackNum;
		}

		if (root->_col == RED && root->_parent->_col == RED)
		{
    
    
			cout << "存在连续的红色节点" << endl;
			return false;
		}

		return PrevCheck(root->_left, blackNum, benchmark)
			&& PrevCheck(root->_right, blackNum, benchmark);
	}

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

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

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

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		Node* ppNode = parent->_parent;

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

		if (_root == parent)
		{
    
    
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
    
    
			if (ppNode->_left == parent)
			{
    
    
				ppNode->_left = subR;
			}
			else
			{
    
    
				ppNode->_right = subR;
			}

			subR->_parent = ppNode;
		}

	}

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

		parent->_left = subLR;
		if (subLR)
		{
    
    
			subLR->_parent = parent;
		}

		Node* ppNode = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		if (_root == parent)
		{
    
    
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
    
    
			if (ppNode->_left == parent)
			{
    
    
				ppNode->_left = subL;
			}
			else
			{
    
    
				ppNode->_right = subL;
			}

			subL->_parent = ppNode;
		}

	}

private:
	Node* _root = nullptr;
};

Applications of red-black trees

Red-black tree is a self-balancing binary search tree that is widely used in various fields of computer science and software engineering due to its balance and efficient performance. Here are some common applications of red-black trees:

  1. std::mapSum in C++ STLstd::setstd::map : Sum in the C++ Standard Template Library (STL) is std::setusually implemented using a red-black tree. They are used to implement ordered associative containers in which key-value pairs are automatically sorted and balanced to support efficient lookup, insertion, and deletion operations.
  2. Database system : Red-black trees are often used in index structures in database systems, such as the implementation of B+ trees. Indexes in databases need to efficiently support operations such as searches, range queries, and sorting, and red-black trees are a good choice for performance.
  3. Operating system : Red-black tree is used in the operating system for process scheduling, virtual memory management and file system implementation. In these scenarios, efficient data structures are needed to manage and operate various system resources.
  4. Network Routing Tables : Red-black trees are widely used for routing table implementation to support efficient route lookup for network devices such as network routers and switches.
  5. Compiler : The compiler can use red-black trees to manage symbol tables for syntax analysis, type checking, and code generation.
  6. Computer graphics : In computer graphics, red-black trees can be used in the implementation of spatial partitioning data structures, such as octrees and quadtrees, to support efficient graphics rendering and collision detection.
  7. High-performance libraries and frameworks : Red-black trees are often used as core data structures in high-performance libraries and frameworks to provide efficient data management and operation functions.
  8. Real-time systems : Real-time systems require efficient data structures to manage tasks and events. Red-black trees can be used to implement timers and schedulers.

In general, the red-black tree is a versatile data structure suitable for any field that requires efficient self-balancing binary search trees, especially scenarios that require efficient insertion, deletion, and search operations. Its balance and performance make it an important tool in a variety of applications.

Comparison of red-black trees and AVL trees

Red-Black Tree (Red-Black Tree) and AVL Tree (Adelson-Velsky and Landis Tree) are both self-balancing binary search trees. They have many similarities in maintaining balance and supporting efficient insertion, deletion and search operations. , but there are some key differences. Here is a comparison between red-black trees and AVL trees:

  1. Balance requirements :
    • Red-black tree: Red-black tree relaxes the balance requirements, which ensures that the height of the tree is at most 2 times.
    • AVL tree: AVL tree has stricter requirements. It ensures that the height difference of the trees does not exceed 1, so the AVL tree is more balanced.
  2. Performance :
    • Red-black trees: Due to relatively low balancing requirements, insertion and deletion operations may be faster than AVL trees on average, so in those scenarios that require frequent insertion and deletion operations, red-black trees may be more suitable.
    • AVL tree: An AVL tree may be slightly faster on lookup operations because it is more tightly balanced, but it may be slower on insertion and deletion operations because it needs to perform rotation operations more frequently to maintain balance.
  3. Rotation operation :
    • Red-black trees: Red-black trees require relatively few rotations because they relax balance requirements. Typically, red-black trees require fewer rotation operations but require more color transformation operations to maintain balance.
    • AVL tree: AVL tree rotation operations are more frequent because it requires tighter balance. This may result in more rotations being performed during insertion and deletion operations.
  4. Memory consumption :
    • Red-black trees: Since red-black trees have lower balancing requirements, they typically consume less memory because no additional balancing factors are needed to keep track of node height differences.
    • AVL tree: AVL tree needs to store balancing factors for each node, which may result in more memory consumption.
  5. Application scenarios :
    • std::mapRed-black tree: Suitable for scenarios that require efficient insertion and deletion operations but have relatively low performance requirements for search operations, such as sum in C++'s STL std::set.
    • AVL tree: Suitable for scenarios that have higher performance requirements for search operations, but can tolerate slower insertion and deletion operations, such as database indexes.

In general, the choice of using a red-black tree or an AVL tree depends on the application needs and performance requirements. In the implementation of C++ map and set, the bottom layer calls the red-black tree, but in actual interviews and work, red-black trees are used. Black trees may have wider applications.

Guess you like

Origin blog.csdn.net/kingxzq/article/details/132830388