This article will take you through the red-black tree --- the red-black tree is so simple

Table of contents

Table of contents

Table of contents

The author has something to say

binary search tree

common operation

        Search (common for red-black trees): to find each node, we start from the root node

        insert:

        Find the minimum value (common for red-black trees):

        Find the maximum value (common for red-black trees):

        Find predecessor nodes (common for red-black trees):

        Find successor nodes (common for red-black trees):

        Traversal (common for red-black trees):

        delete:

BST Question Continuation

    

AVL tree

AVL tree achieves balance

With the AVL tree, why do we need a red-black tree?

An illustration of building an AVL tree

left-handed

Right-handed

2-3-4 tree

2-3-4 tree construction process

2-3-4 Tree to Red-Black Tree Conversion Rules

red black tree

      definition

      5 principles of red-black tree against 2-3-4 tree:

        common operation

        Discoloration:

        Left hand:

        Right rotation:

        Added:

Delete (difficulty):

Adjustment after deletion       

        Summary description:

Red-black tree video reference: 14-Red-black tree core operation-adjustment after deletion-brother nodes can be realized by borrowing the situation and code_哔哩哔哩_bilibili

Handwritten red-black tree (code)

The author has something to say

        If you want to play with red-black trees, you must have a certain understanding of the imitation gameplay. When we play the Rubik's Cube, as long as we follow the rules, no matter how complicated or simple your process and steps are, you can finally make each face only need one color to settle successfully. (Please leave the stick: take the 3*3 Rubik's Cube as an example). The same is true for playing red-black trees, as long as you follow the rules (left-handed, right-handed, color-changing), no matter what the process is in your middle, you can finally write a red-black tree (the order of steps for each person may be different, but the principle is the same ).

        It is too laborious to draw pictures by computer, so I will draw some pictures below on paper, hope you can understand. If you have a good drawing tool recommendation, you can comment or send a private message.

        I have been looking for a red-black tree code for a long time, but I have not found a complete one. . . After helplessness, write a red-black tree code by yourself ==> the most complete code of c++ red-black tree in the whole network

binary search tree

         Speaking of red-black tree, we must first know its ancient ancestor: binary search tree

        Definition: It is a binary tree whose left node is smaller than the parent node and whose right node is larger than the parent node. His height determines the search efficiency.

         Binary search trees are sorted. It should be said that all binary search trees and variants are sorted. How to say, it is sorted? The following can explain: the binary search tree is based on the idea of ​​binary, the purpose is to speed up the search efficiency. The general thing to do before binary is to sort first, and it is impossible to binary a sequence without order.

Let's look at the rules of binary search tree sorting: when we establish a coordinate system on the periphery of a binary search tree, we will find a magical phenomenon.

Isn't it, in fact, it is sorted on the X coordinate, and then divided into two. Including what we call red-black trees.

Extension: Thinking about pre-order traversal, in-order traversal, and post-order traversal of trees. Regardless of convenience, all access sequences are the same but the timing of printing is different. Verify this yourself, ha. 

common operation

        Search (common for red-black trees): to find each node, we start from the root node

        1. If the lookup value is greater than the current value, search the right subtree

        2. If the search value is equal to the current value, stop searching and return to the current node

        3. If the search value is smaller than the current value, search the left subtree

        insert :

        To insert a node, the insertion node position must first be found. The comparison is still from the root node. If it is smaller than the root node, it will be compared with the left subtree. Otherwise, it will be compared with the right subtree. Until the left subtree is empty or the right subtree is empty, it will be inserted into the corresponding empty position.

       Find the minimum value (common for red-black trees):

        Search all the way along the left subtree of the root node until the last node that is not empty, which is the largest node of the current tree.

        Find the maximum value (common for red-black trees):

        Search all the way along the right subtree of the root node until the last node that is not empty, which is the largest node of the current tree.

       Find predecessor nodes (common for red-black trees):

        less than the maximum value of the current node

        Find successor nodes (common for red-black trees):

        greater than the minimum value of the current node

        Traversal (common for red-black trees):

        Visit the entire tree according to a certain order, common are pre-order traversal, in-order traversal, post-order traversal

        delete :

        In essence, it is to find a predecessor node or a successor node to replace

        There are three cases:

        1. Leaf nodes are deleted directly

                      2. If there is only one child node, replace it with a child node

          3. If there are two child nodes, you need to find a replacement node (predecessor node or successor node)

        As for why we need to find a predecessor node or a successor node, we return to the definition of a binary search tree: it is a binary tree whose left node is smaller than the parent node and whose right node is larger than the parent node. The property can be kept unchanged through the predecessor node and the successor node. Later, when the red-black tree is deleted, this method is also used. The difference is that the red-black tree needs to ensure the black balance, so it needs to be adjusted.

BST Question Continuation

        Let's go back and talk about a special case of the next binary search tree: a skewed binary search tree, the height of this tree is N

    

   When we insert a set of elements sequentially, the binary tree degenerates into a linked list, and when it degenerates into a linked list, the search efficiency becomes O(n).

        Let’s expand here to say how big the difference is between O(n) and O(logN), let’s draw a function curve:

        

        In other words, Big O notation expresses the relationship as a function of N. So when we are thinking about it, we should consider it according to the size of the data. When there is very little data, it is faster to use a linked list.

        Back to BST, based on the problems of BST, a balanced binary search tree was produced. When inserting and deleting a balanced tree, the height will be kept at logN through the rotation operation. Two representative balanced trees are AVL trees (height balance has all the properties of a binary search tree, and the height difference between the left and right subtrees is the same. over 1) and red-black trees.

        How to choose AVL tree or red-black tree:

        When there are many search operations and almost no insertion and deletion operations, it is more efficient to choose an AVL tree than a red-black tree.

        When there are many insertion and deletion operations, it is more appropriate to choose a red-black tree. (You can understand it as: red-black tree is a low-profile compromise solution)

AVL tree

        I didn’t particularly want to talk about the AVL tree. Now that I’ve written it here, let’s talk about it by the way, and explore the cost of the AVL tree in order to maintain balance. Height balance has all the properties of a binary search tree, and the height difference between the left and right subtrees does not exceed 1

AVL tree achieves balance

        Through left-handed and right-handed (under the description: left-handed and right-handed must not break the search rules of the binary search tree)

With the AVL tree, why do we need a red-black tree?

        AVL tree is not as good as red-black tree (red-black tree only keeps black nodes balanced) due to its complex implementation and poor insertion and deletion performance.

An illustration of building an AVL tree

     

         It seems that I forgot to mention the operation of left rotation and right rotation in advance (very simple, and the code is also simple to implement)

left-handed

        How to express it, how to make animations. Let me draw it by hand, write an example

        

         Don't know image wave? Imagine that someone pulls down along the line 3-5-6 (wanting to set the pulley), then 5 goes down and 6 goes up. At this time, 5 becomes a child node of 6, and 6 has three child nodes that obviously do not conform to the binary tree. In order to maintain the basic properties of the binary search tree, it is most suitable for 5.5 to become the right child of 5.

Right-handed

        Contrary to left rotation, you can draw a picture yourself to experience it.

2-3-4 tree

        Let me talk about it first, why write a 2-3-4 tree first. Because the nature of the 2-3-4 tree can deduce the nature of the red-black tree, I heard that the red-black tree is a 2-3-4 tree, I don’t know if it is true. Anyway, it is convenient for us to understand that the red-black tree is real. Of course, we must understand that we can’t memorize it by force, so it is not convenient to optimize according to the actual situation, and it is easy to forget.

        The 2-3-4 tree is a fourth-order B-tree (Balance Tree), which belongs to a multi-way search tree, and its structure has the following restrictions.

        1. All leaf nodes have the same height (depth)

        2. The node can only be one of 2-node, 3-node and 4-node

        Supplementary Note: 

        2-node: 2 children can be mounted under this node

        3-node: 3 children can be mounted under this node

        4-node: 4 children can be mounted under this node

        We draw a graph to represent: 

   

    3. The elements always maintain the sort order, and maintain the nature of the binary tree as a whole, that is, the parent node is greater than the left child node and smaller than the right child node; and when a node has multiple elements, each element must be greater than the element of its left subtree. (Correct the error in the above picture: 2-node, 3-node, 4-node are a whole)

Look at a complete 2-3-4 tree:

The following is a complaint, writing a blog is really not easy. Look forward to watching and supporting the following. . . . .

        Digression: The query operation of 2-3-4 tree is very simple like ordinary binary search tree, but because of the uncertain number of node elements, it is not convenient to implement in some programming languages. Equivalence - red-black tree. A 2-3-4 tree can correspond to multiple red-black trees, and a red-black tree can only correspond to one 2-3-4 tree.

2-3-4 tree construction process

        Looking at the picture, it is worth noting that the splitting process is upward . That is to say, the construction of this tree is different from what we imagined. The whole tree grows upwards.

2-3-4 Tree to Red-Black Tree Conversion Rules

        I heard that the red-black tree originated from the 2-3-4 tree, and the essence of the red-black tree is a 2-3-4 tree. I don't know if it's right, but the 5 rules of the red-black tree can indeed be obtained through the 2-3-4 tree.

        Key points: The level rules from 2-3-4 trees to red-black trees are the key points, which run through the entire following content. Remember these four equivalence relations, and we will use them to deduce the five principles of red-black trees through 2-3-4 trees in a while.

        There are 4 equivalence relations:

        2 nodes:

         

        3 nodes:

         

        4 nodes:

        

        Fission relationship:

        

        Convert a 2-3-4 tree into a red-black tree by the above hierarchical principle:

        Conclusion: A 2-3-4 tree corresponds to multiple red-black trees, and a red-black tree corresponds to only one 2-3-4 tree

red black tree

      definition

        A red-black tree is a binary search tree with color attributes for nodes, but it has the following five properties in addition to the binary search tree:

        1. The node is red or black

        2. The root is black

        3. All leaves are black (leaves are NULL nodes, such nodes cannot be ignored)

        4. Every red node must have two black child nodes. (There cannot be two consecutive red nodes on all paths from each leaf to the root)

        5. All simple paths from any node to each of its leaves contain the same number of black nodes (black balance)

        The following figure is a typical red-black tree:

        

         Many articles introduce red-black trees and end here. In fact, this is not convenient for us to really understand red-black trees. We need to understand to ensure that we can integrate later.

      5 principles of red-black tree against 2-3-4 tree:

        1. The node is red or black ===> self-evident

        2. The root is black

                Here we need to divide into 3 situations: the root is 2 nodes, the root is 3 nodes, and the root is 4 nodes

                2.1 The root is 3 nodes

                This is the case in 2.1.0   . Let's review the rules for converting the 3 nodes of the 2-3-4 tree into a red-black tree

                2.1.1 The 3 nodes of the 2-3-4 tree correspond to the red-black tree conversion: (top black and bottom red)

                

                 2.1.2 Through transformation, we can know that when the root of the 2-3-4 tree is 3 nodes, the root of the corresponding red-black tree must be black

                 2.2 root is 2 nodes

                 This is the case in 2.2.0   , let's review the rules for converting 2 nodes of a 2-3-4 tree into a red-black tree

                 2.2.1 2 nodes of 2-3-4 tree correspond to red-black tree conversion: (directly converted to black)

                 

                 2.2.2 Through conversion, we can know that when the root of the 2-3-4 tree is 2 nodes, the root of the corresponding red-black tree must always be black

                 2.3 The root is 4 nodes

                 This is the case in 2.3.0   . Let's review the rules for converting the 4 nodes of the 2-3-4 tree into a red-black tree

                 2.3.1 4 nodes of 2-3-4 tree correspond to red-black tree conversion: (upper black and lower red)

                 

                 2.3.2 Through transformation, we can know that when the root of the 2-3-4 tree is 4 nodes, the root of the corresponding red-black tree must always be black

        3. All leaf nodes are black ===> all 2 nodes are converted into red-black trees are black

        4. Every red node must have two black child nodes ===> in order to maintain black balance (such as rule 5)

                We derive this conclusion from the 2-3-4 tree

                4.1 When the red node is a pseudo-leaf node (here we call it so for easy understanding)

                 

                 This situation is easy to understand. Let's review the rule 3 of the red-black tree [All leaves are black (leaves are NULL nodes, such nodes cannot be ignored)]

                4.2 First explain the situation that the 2-3-4 tree will undergo fission. Only when there are already 3 elements in a certain node of the 2-3-4 tree and a new element needs to be inserted into this node, then it will split upwards.

                The whole process of fission is divided into: 3 node conversion --> inserting a new node (the newly inserted node must be red) --> judging whether it is the root node (the root node needs to turn black).  

                

        5. All simple paths from any node to each of its leaves contain the same number of black nodes

                This rule is also called black balance, and it is an important property for red-black trees to maintain black balance . Through some of the above rules and conventions, we can find that the red-black tree we built has satisfied the black balance. In other words, it may be more accurate: the red-black tree only maintains the black balance, and does not pay attention to the red nodes (this property will be realized thoroughly during the deletion operation). It is precisely because of the black balance nature of the red-black tree that it prevents the degeneration into a linked list like a binary search tree, and also reduces the cost of adjustment like an AVL tree to maintain a high balance. So it can be understood as a compromise of the AVL tree.

                In the actual project development process, it must be reasonable to blindly choose red-black tree as the underlying data structure? The answer is obviously not, when the number of our lookups is much greater than the insertion and deletion operations. We can choose the AVL tree . The height balance of the AVL tree is lower than that of the red-black tree to a certain extent, and the search efficiency is higher. If there are frequent insertion and deletion operations in the project, it is necessary to select the post-red-black tree , and AVL will pay a high price to maintain a high balance.

        common operation

       We have introduced the five major rules of the red-black tree, and then we will look at the basic operations of the red-black tree (left-handed and right-handed have been introduced before, here we will introduce it in more detail).

        Discoloration :

        The color of the node changes from black to red or from red to black

        Left hand :

         Taking a certain node as the rotation point, its right child node becomes the parent node of the rotation node, the left child node of the right child node becomes the right child node of the rotation node, and the left child node remains unchanged.

        Right rotation :

        With a certain node as the rotation point, its left child node becomes the parent node of the rotation node, the right child node of the left child node becomes the left child node of the rotation node, and the right child node remains unchanged.

       Added:

        Discuss according to the situation, mainly to find the insertion position, and then self-balance (left-handed or right-handed) and the inserted node is red (if it is red, then there will be an extra black node on the current branch, thus destroying the black balance) .

                Give an example: take the left subtree as an example, the situation of the right subtree is the opposite.                                                           

                a. If the first node (root node) is inserted, red becomes black.                                                      

                b. If the parent node is black, insert it directly without changing the color.                                                           

                c. If the parent node is red and the uncle node is also red (at this time, the grandpa node must be black), then the parent node and uncle node become black, and the grandpa node becomes red (if the grandpa node is the root node, then it becomes black again) , the grandpa node needs to be recursive at this time (compare the grandpa node as a newly inserted node again)                                                                                        

                Here is an explanation, why in this case, the grandfather must be black. It is impossible to have two consecutive reds in a red-black tree, because a red node must be connected to two two black child nodes.

  Insert 3 because 5 is the root node and needs to be black again

Insert 2, 3 and 4 turn black, 4 turns red. Satisfied black balance, recursion stops.

                d. If the parent node is red, there is no uncle node or the uncle node is black (it can only be a NULL node at this time), at this time, the grandpa node is used as the fulcrum to rotate right. After the rotation, the original grandpa node turns red, and the original father node becomes black.                   

         insert 1   

        It is not difficult to see that the whole process is divided into two parts: adding nodes and adjusting after adding (see code)       

        Because the addition and deletion are relatively simple and similar to the delete operation, we will not introduce them in detail here. Let's focus on the delete operation. If you understand the delete operation, you will naturally understand the new operation (friends who still don't understand, you can leave a message or private message to discuss together). 

Delete (difficulty):

        In layman's terms, there are three sentences (there are three types of big cases) --> If you can handle it yourself, if you can't handle it, ask your brother and father for help. (Father and brother self damage nodes)

        In fact, deleting and adding nodes are similar: delete nodes, adjust after deletion (keep the rules of red-black tree)

        Let's talk in detail how to delete it is reasonable?

        There are three situations in the deletion operation: these three situations are divided according to the situation of the red-black tree node

        1. The leaf node is deleted, delete it directly (for example: delete 2, 6, 13, 18 ) --> after deletion, it is still a red-black tree

        2. The deleted node has a child node, then use the child node to replace the deleted node (for example: delete 4) --> After the child node is replaced, it is still a red-black tree

        3. The deleted node has 2 child nodes. At this time, it is necessary to find a predecessor node or a successor node to replace it.

                This situation is more complicated: we usually cover the node to be deleted through the predecessor node or the successor node, and then delete the predecessor node or the successor node.

                Why do you do this?

                For example, in the picture above: Now we need to delete the node with key = 10. If you delete key = 10 directly, you need to disconnect 10, and then use key = 6 or key = 13 to replace the position of key = 10, and then you need to delete the key = 6 or key = 13 nodes are deleted. Such a price is obviously relatively high.

                Supplementary note: the predecessor node is the maximum value smaller than the current node, and the successor node is the minimum value greater than the current node

                There are only 2 cases of the deleted predecessor node or successor node: (back to 1 and 2 cases)

                a. The deleted node is a leaf node

 b. The deleted node has only one child (as shown in the figure below, either the left child or the right child)

                      c. In a special case, when there is only one root node, it can be deleted directly (root = NULL)

Adjustment after deletion       

The delete operation corresponds to the relationship of the 2-3-4 tree

        According to the above explanation, we know that the deleted node can only be a leaf node or a non-leaf node with one child node.

        We then analyze: the leaf nodes of the red-black tree and the upper layer of the leaf nodes must correspond to the leaf nodes of the 2-3-4 tree, so the deletion of the corresponding 2-3-4 tree must delete the leaves of 2-3-4 node. [Supplementary note: 2-3-4 tree is split upwards, so 2-3-4 tree is a full binary tree, according to the conversion of 2-3-4 tree, the red-black tree non-leaf nodes and the upper layers of non-leaf nodes Other nodes must have two children]

In fact, it is not difficult for us to see that the 2-3-4 tree itself maintains a black balance (2-3-4 trees have no color, don’t be misunderstood)

 Let's look at the picture above. It is legal for us to delete 0, 1, 7, 7.5, 9, 10, and 11. In human terms, in the 2-3-4 tree, it is legal for us to delete an element in the 3-node and 4-node, and the deletion still satisfies the 2-3-4 tree.

What if I delete key = 3, 4, 5? When we delete key = 3, the node with key = 2 needs to have two children, which is no longer a 2-3-4 tree. At this time, it is necessary to borrow from the brother through the parent node. If the brother cannot borrow, the brother or father node can borrow it by itself.

        Summary description:

        a. Do what you can do by yourself

  1. If the deleted node corresponds to node 3 or node 4 of the 2-3-4 tree, delete it directly without borrowing from sibling and father nodes.

     Corresponding to red-black tree: If the red node is deleted, delete it directly. If you delete a black node, replace it with a red node, and just turn it black, no adjustment is required. [1, 2 of red-black tree deletion situation]

        b. If you can’t figure it out, ask your brother to borrow it. If your brother doesn’t want to borrow it, ask your father to borrow it. When the father comes down, the brother finds an element to replace the father [guaranteed sorting on the x-axis]

 b.1 The premise is to find the "real" sibling node

 b.2 Brothers have to borrow (the brother node must be black at this time, if it is red, it means that this node is not a real brother node, you need to go back to the previous step to find the real brother node)

        If the brother node has two child nodes (the two child nodes must be red, if it is black, it means that the brother node corresponds to 2 nodes in the 2-3-4 tree at this time, and it is impossible to have redundant elements to borrow), at this time it is necessary Rotate to change color.

        When the brother node has only one child node, it needs to rotate and change color

        c. Father and brother damage nodes

        Brother nodes have no extra elements to borrow (at this time, brother nodes must be black 2 nodes), and at this time, the branch where the brother nodes are located must also damage a black node to achieve black balance. The fastest way is to directly turn the brother node red (quite So it is to reduce a black node), and at this time the subtree whose parent node is the root has reached a balance again (both sides have one less black than before). However, the tree rooted at the grandparent node is still unbalanced, and recursive processing is required at this time.

handwritten red black tree

main.cpp ==> for testing

#include <iostream>
#include "RBTree.h"

using namespace std;


int main()
{

	RBTree* tree1 = new RBTree();

	int key = 0;
	int value = 0;

	// 插入
	for(int i = 0; i < 10; i++)
	{
		key = i + 1;
		value = i + 1;
		tree1->put(key, value);
		/*tree1->printTree();*/
	}

	// 删除
	tree1->printTree();
	while (1)
	{
		cin >> key;
		tree1->remove(key);
		tree1->printTree();
	}


	delete tree1;

	return 0;
}

RBTree.h

#pragma once

#define BLACK true
#define RED   false

struct RBNode
{
	RBNode* parent;
	RBNode* left;
	RBNode* right;

	bool color;
	int  key;
	int  value;

	RBNode()
	{

	}

	RBNode(int key, int value, RBNode* parent)
	{
		this->key = key;
		this->parent = parent;
		this->value = value;
		this->left = nullptr;
		this->right = nullptr;
		this->color = BLACK;
	}

	// 比较器
};

class RBTree
{
public:
	RBTree():root(nullptr)
	{
	}

	~RBTree()
	{

	}


	// 新增
	void put(int k, int v);

	// 删除
	int remove(int key);

	// 打印
	void printTree();

private:

	// 左旋
	void leftRotate(RBNode* p);

	// 右旋
	void rightRotate(RBNode* p);


	// 新增调整
	void fixAfterPut(RBNode* x);

	// 寻找前驱节点
	RBNode* predecessor(RBNode* node);

	// 寻找后继节点
	RBNode* sucessor(RBNode* node);

    // 找到key对应的位置
	RBNode* getNode(int key);

	// 删除
	void deleteNode(RBNode* node);

	// 删除后调整
	void fixAfterRemove(RBNode* x);

	// 父节点
	RBNode* parentOf(RBNode* node);

	// 右孩子
	RBNode* rightOf(RBNode* node);

	// 左孩子
	RBNode* leftOf(RBNode* node);

	// 改变颜色
	void setColor(RBNode* node, bool color);

	// 获取颜色
	bool colorOf(RBNode* node);

	// 销毁红黑树
	void deleteTree();

private:

	RBNode* root;

};

RBTree.cpp

#include "RBTree.h"
#include <queue>
#include <iostream>
#include <stack>

/*
-----------------------------------------
	围绕p左旋:
			p                       r
		   / \                    /  \
		 pl   r         ===>   p     rr
			 /  \              / \
			rl   rr          pl  rl
-----------------------------------------
*/
void RBTree::leftRotate(RBNode* p)
{

	if (p != nullptr)
	{
		RBNode* r = p->right;
		p->right = r->left;
		
		if (r->left != nullptr)
		{
			r->left->parent = p;
		}
		r->parent = p->parent;

		// 如果p是根节点
		if (p->parent == nullptr)
		{
			root = r;
		}
		else if(p->parent->left == p)  // p是左孩子
		{
			p->parent->left = r;
		}
		else  // p是右孩子
		{
			p->parent->right = r;
		}
		r->left = p;
		p->parent = r;
	}
}

/*
------------------------------------------
	围绕p右旋:
			p                      pl
		   / \                    /  \
		 pl   r         ===>   p     p
	    /  \                  /     / \    
	   rl   rr               rl    rr  r
-----------------------------------------
*/
void RBTree::rightRotate(RBNode* p)
{
	if (p != nullptr)
	{
		RBNode* l = p->left;
		p->left = l->right;

		if (l->right != nullptr)
		{
			l->right->parent = p;
		}
		l->parent = p->parent;

		// 如果p是根节点
		if (p->parent == nullptr)
		{
			root = l;
		}
		else if (p->parent->right == p)  // p是左孩子
		{
			p->parent->right = l;
		}
		else  // p是右孩子
		{
			p->parent->left = l;
		}

		l->right = p;
		p->parent = l;
	}
}

void RBTree::put(int k, int v)
{
	RBNode* t = root;

	// 如果是根节点
	if (t == nullptr)
	{
		root = new RBNode(k, v, nullptr);
		return;
	}

	// 需要插入位置
	// 定义一个双亲指针
	RBNode* parent = nullptr;
	// 沿着跟节点寻找插入位置
	do
	{
		parent = t;

		if(k < parent->key)
		{
			t = t->left;
		}
		else if(k > parent->key)
		{
			t = t->right;
		}
		else
		{
			t->value = v;
			return;
		}

	} while (t != nullptr);

	// t == nullptr 说明没有找到相同的节点

	RBNode* e = new RBNode(k, v, parent);

	// 挂在左孩子上
	if (k < parent->key)
	{
		parent->left = e;
	}
	else
	{
		parent->right = e;
	}

	// 调整
	fixAfterPut(e);

}

/*
    1. 2-3-4树:新增元素+2节点合并(节点中只有1个元素) = 3节点(节点中有2个元素)
	   红黑树:新增一个红色节点+黑色父节点 = 上黑下红  (不需要调整)
	2. 2-3-4树: 新增元素+3节点合并(节点中只有2个元素) = 4节点(节点中有3个元素)
	   这里有6种情况:(左3、右3需要调整 | 左2右1、右2左1需要调整 | 其他2种情况不需要调整)
	   红黑树:新增一个红色节点+上黑下红 = 排序后中间节点是黑色,两边是红色(3节点)
	3. 2-3-4树: 新增一个元素+4节点合并(节点中只有3个元素) = 原来的4节点分裂,中间元素升级为父节点,新增节点与剩下的节点合并
	   红黑树:新增一个红色节点+爷爷节点黑色,父节点和叔叔节点都是红色 = 爷爷节点变红,父亲和叔叔节点变黑,如果爷爷节点是根节点,则爷爷节点变黑
*/

void RBTree::fixAfterPut(RBNode* x)
{
	x->color = RED;

	// 如果父节点是黑色就不需要调整
	while (x != nullptr && x != root && x->parent->color == RED)
	{
		//1.左3: x的父节点是爷爷的左孩子
		if (parentOf(x) == leftOf(parentOf(parentOf(x)))) 
		{
			// 叔叔节点
			RBNode* y = rightOf(parentOf(parentOf(x)));
			// 如果叔叔节点是红色:情况3
			if (colorOf(y) == RED)
			{
				setColor(parentOf(x), BLACK);
				setColor(y, BLACK);
				setColor(parentOf(parentOf(x)), RED);
				x = parentOf(parentOf(x));
			}
			else  // 情况2
			{
				// 左2右1
				if (x == rightOf(parentOf(x)))
				{
					RBNode* p = parentOf(x);
					int tmpkey = p->key;
					int tmpvalue = p->value;
					p->key = x->key;
					p->value = y->value;
					x->key = tmpkey;
					y->value = tmpvalue;

					leftRotate(parentOf(x));
				}

				setColor(parentOf(x), BLACK);
				setColor(parentOf(parentOf(x)), RED);
				rightRotate(parentOf(parentOf(x)));
			}
		}
		else
		{
			// 叔叔节点
			RBNode* y = leftOf(parentOf(parentOf(x)));
			// 如果叔叔节点是红色:情况3
			if (colorOf(y) == RED)
			{
				setColor(parentOf(x), BLACK);
				setColor(y, BLACK);
				setColor(parentOf(parentOf(x)), RED);
				x = parentOf(parentOf(x));
			}
			else  // 情况2
			{
				// 左2右1
				if (x == leftOf(parentOf(x)))
				{
					RBNode* p = parentOf(x);
					int tmpkey = p->key;
					int tmpvalue = p->value;
					p->key = x->key;
					p->value = y->value;
					x->key = tmpkey;
					y->value = tmpvalue;

					rightRotate(parentOf(x));
				}

				setColor(parentOf(x), BLACK);
				setColor(parentOf(parentOf(x)), RED);
				leftRotate(parentOf(parentOf(x)));
			}
		}	
	}

	root->color = BLACK;
}

RBNode* RBTree::predecessor(RBNode* node)
{
	if (node == nullptr)
	{
		return nullptr;
	}
	
	if(node->left != nullptr)
	{
		RBNode* p = node->left;
		while (p->right != nullptr)
		{
			p = p->right;
		}

		return p;
	}
	else
	{
		RBNode* p = node->parent;
		RBNode* child = node;

		while (p != nullptr && child == p->left)
		{
			child = p;
			p = p->parent;
		}

		return p;
	}
}

RBNode* RBTree::sucessor(RBNode* node)
{
	if (node == nullptr)
	{
		return nullptr;
	}

	if (node->right != nullptr)
	{
		RBNode* p = node->right;
		while (p->left != nullptr)
		{
			p = p->left;
		}

		return p;
	}
	else
	{
		RBNode* p = node->parent;
		RBNode* child = node;

		while (p != nullptr && child == p->right)
		{
			child = p;
			p = p->parent;
		}

		return p;
	}
}

int RBTree::remove(int key)
{
	RBNode* node = getNode(key);
	if (node == nullptr)
	{
		return -1;
	}
    
	int val = node->value;

	deleteNode(node);

	return val;

}

RBNode* RBTree::getNode(int key)
{
	RBNode* node = root;

	while (root != nullptr)
	{
		if (key < node->key)
		{
			node = node->left;
		}
		else if(key > node->key)
		{
			node = node->right;
		}
		else
		{
			return node;
		}
	}

	return nullptr;
}

/*---------------------------------
删除操作:
1.删除叶子节点,直接删除
2.删除的节点有一个子节点,那么用子节点来替换
3.如果删除的节点有2个子节点,此时需要找到前驱节点或者后继节点来替代
-----------------------------------*/
void RBTree::deleteNode(RBNode* node)
{
	// 3.node节点有2个孩子
	if (node->left != nullptr && node->right != nullptr)
	{
		RBNode* sucessorNode = sucessor(node);
		node->key = sucessorNode->key;
		node->value = sucessorNode->value;
		node = sucessorNode;
	}

	RBNode* replacement = node->left != nullptr ? node->left : node->right;

	// 2.替代节点不为空
	if (replacement != nullptr)
	{
		// 替代者的父指针指向原来的node的父亲
		replacement->parent = node->parent;
		if (node->parent == nullptr)
		{
			root = replacement;
		}
		else if(node == node->parent->left)   // node是左孩子,所以替代者依然是左孩子
		{
			node->parent->left = replacement;
		}
		else // node是右孩子,所以替代者依然是右孩子
		{
			node->parent->right = replacement;
		}
        
		// 替换完之后需要调整平衡
		if (node->color == BLACK)
		{
            // 需要调整,此时替代节点一定是红色(替代节点一定是红色,此时只要变色)
			fixAfterRemove(replacement);
		}

		// 将node的左右孩子指针和父指针都指向null
		node->left = node->right = node->parent = nullptr;
		delete node;

	}
	else if(node->parent == nullptr)  // 根节点
	{
		node->left = node->right = node->parent = nullptr;
		delete node;
		root = nullptr;
	}
	else  // 1.叶子节点
	{
		
		// 调整
		if (node->color == BLACK)
		{
			fixAfterRemove(node);
		}
		
	    // 删除
		if (node->parent != nullptr)
		{
			if (node == node->parent->left)
			{
				node->parent->left = nullptr;
			}
			else if(node == node->parent->right)
			{
				node->parent->right = nullptr;
			}

			// node->parent = nullptr;
			node->left = node->right = node->parent = nullptr;
			delete node;
		}
	}
}

void RBTree::fixAfterRemove(RBNode* x)
{
	//情况2/3:
	while (x != root && colorOf(x) == BLACK) 
	{
		if (x == leftOf(parentOf(x)))
		{
			RBNode* rnode = rightOf(parentOf(x));
			
			// 判断此时兄弟节点是不是真正的兄弟几点
			if (colorOf(rnode) == RED)
			{
				setColor(rnode, BLACK);
				setColor(parentOf(x), RED);
				leftRotate(parentOf(x));

				// 找到真正的兄弟节点
				rnode = rightOf(parentOf(x));
			}
			
			// 情况3:兄弟没得借
			if (colorOf(leftOf(rnode)) == BLACK && colorOf(rightOf(rnode)))
			{
				setColor(rnode, RED);
				x = parentOf(x);
			}
		    // 情况2:自己搞不定,需要向兄弟借,兄弟有得借
			else
			{
				// 分两种小情况:兄弟节点本来是3节点或者4节点
				if (colorOf(rightOf(rnode)) == BLACK)
				{
					setColor(leftOf(rnode), BLACK);
					setColor(rnode, RED);
					rightRotate(rnode);
					rnode = rightOf(parentOf(x));
				}
				
				// 设置颜色
				setColor(rnode, colorOf(parentOf(x)));
				setColor(parentOf(x), BLACK);
				setColor(rightOf(rnode), BLACK);
				leftRotate(parentOf(x));
				x = root;
			}
		}
		else // x是右孩子
		{
			RBNode* lnode = leftOf(parentOf(x));

			// 判断此时兄弟节点是不是真正的兄弟几点
			if (colorOf(lnode) == RED)
			{
				setColor(lnode, BLACK);
				setColor(parentOf(x), RED);
				rightRotate(parentOf(x));

				// 找到真正的兄弟节点
				lnode = leftOf(parentOf(x));
			}

			// 情况3:兄弟没得借
			if (colorOf(rightOf(lnode)) == BLACK && colorOf(leftOf(lnode)))
			{
				setColor(lnode, RED);
				x = parentOf(x);
			}
			// 情况2:自己搞不定,需要向兄弟借,兄弟有得借
			else
			{
				// 分两种小情况:兄弟节点本来是3节点或者4节点
				if (colorOf(leftOf(lnode)) == BLACK)
				{
					setColor(rightOf(lnode), BLACK);
					setColor(lnode, RED);
					leftRotate(lnode);
					lnode = leftOf(parentOf(x));
				}

				// 设置颜色
				setColor(lnode, colorOf(parentOf(x)));
				setColor(parentOf(x), BLACK);
				setColor(leftOf(lnode), BLACK);
				rightRotate(parentOf(x));
				x = root;
			}
		}
	}

	//情况1:替代节点是红色,则直接染黑,补偿删除的黑色节点,这样红黑树依然保持平衡
	//递归补偿
	setColor(x, BLACK);
}

RBNode* RBTree::parentOf(RBNode* node)
{
	return node != nullptr ? node->parent : nullptr;
}


RBNode* RBTree::rightOf(RBNode* node)
{
	return node != nullptr ? node->right : nullptr;
}

RBNode* RBTree::leftOf(RBNode* node)
{
	return node != nullptr ? node->left : nullptr;
}

bool RBTree::colorOf(RBNode* node)
{
	// 如果为空,实际上是黑色
	return node == nullptr ? BLACK : node->color;
}

void RBTree::setColor(RBNode* node, bool color)
{
	if (node != nullptr)
	{
		node->color = color;
	}
}

// 底层遍历
void RBTree::printTree()
{

	std::queue<RBNode*> que;
	que.push(root);
	
	while (!que.empty())
	{
		RBNode* node = que.front();
		que.pop();

		if (node->color == RED)
		{
			std::cout << node->key << "(R)" << "  ";
		}
		else
		{
			std::cout << node->key << "(B)" << "  ";
		}
		
		if (node->left != nullptr)
		{
			que.push(node->left);
		}

		if (node->right != nullptr)
		{
			que.push(node->right);
		}
	}
	
}

void RBTree::deleteTree()
{
	std::stack<RBNode*> sta;
	std::queue<RBNode*> que;

	while (!que.empty())
	{
		RBNode* node = que.front();
		que.pop();

		sta.push(node);

		if (node->left != nullptr)
		{
			que.push(node->left);
		}

		if (node->right != nullptr)
		{
			que.push(node->right);
		}
	}

	while (!sta.empty())
	{

		RBNode* tmp = sta.top();
		sta.pop();

		tmp->left = tmp->right = tmp->parent = nullptr;
		delete tmp;
	}

}

Guess you like

Origin blog.csdn.net/weixin_46120107/article/details/128421692