• 数据结构之<RBTree >


    🌈前言

    本篇文章进行数据结构中RBTree(红黑树)的学习!!!


    🌅红黑树

    🌷1、RBTree的概念

    概念:

    • 红黑树:它是二叉搜索树的一种,区别在于在每个结点上增加一个存储位表示结点的颜色,可以是 “RED” 或 “RLACK”
    • 它通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是"接近"平衡的

    在这里插入图片描述

    红黑树的规则:

    1. 根节点一定为黑色
    2. 如果一个节点为红色,则它的左右孩子均为黑色(没有连续的红节点)
    3. 对于每个结点,从该结点到其所有后代的叶结点的路径上,均包含相同数目的黑色结点
    4. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
    5. 红黑树的最长路径的黑色节点不超过最短路径的二倍

    🌸2、RBTree节点的定义

    // 红黑树的颜色
    enum Color
    {
    	RED,
    	BLACK
    };
    
    // RBTree的节点
    template <typename K, typename V>
    struct RBTreeNode
    {
    	RBTreeNode(pair<K, V> _kv)
    		: left(nullptr)
    		, right(nullptr)
    		, parent(nullptr)
    		, kv(_kv)
    		, col(RED)
    	{}
    
    	RBTreeNode<K, V>* left;   // 节点的左孩子
    	RBTreeNode<K, V>* right;  // 节点的右孩子
    	RBTreeNode<K, V>* parent; // 节点双亲
    	pair<K, V> kv;  // 数值
    	Color col;		// 颜色
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    注意:RB树节点与avl树节点相似,不同的是增加了节点的颜色


    🌰3、RBTree的插入

    红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:

    1. 按搜索树的规则进行插入新的节点
    // 红黑树的规则:1.根节点是黑色的,黑节点的左右孩子可能为红也可能为黑
    //			   2.如果一个节点为红色,则它的左右孩子节点为黑色(没有连续的红节点)
    //			   3.对于每个节点,从该节点到其所有后代叶节点的路径上,均包含相同的数目的黑色节点(每条路径包含相同数目的黑色节点)
    //			   4.叶节点(nullptr)是黑色的
    
    bool insert(const pair<K, V>& kv)
    	{
    		if (root == nullptr)
    		{
    			root = new Node(kv);
    			root->col = BLACK; // 根节点为黑色
    			return true;
    		}
    
    		// 按搜索树规则插入
    		Node* cur = root;
    		Node* parent = nullptr;
    
    		while (cur != nullptr)
    		{
    			if (cur->kv.first > kv.first) {
    				parent = cur;
    				cur = cur->left;
    			}
    			else if (cur->kv.first < kv.first) {
    				parent = cur;
    				cur = cur->right;
    			}
    			else {
    				return false;
    			}
    		}
    
    		cur = new Node(kv);
    		// 如果插入节点是红色,可能破坏规则2  ---  如果插入节点是黑色,一定会破坏规则3(3很难处理)
    		cur->col = RED;
    		if (parent->kv.first > kv.first) {
    			parent->left = cur;
    		}
    		else {
    			parent->right = cur;
    		}
    		cur->parent = parent;
    		// ...
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    注意:

    • 插入新节点的颜色必须为红色,因为是红色的话可能会破坏规则2
    • 如果插入节点的颜色是黑色,则必定破坏规则3,规则3最难处理,最好别惹

    在这里插入图片描述


    1. 检测新节点插入后,红黑树的性质是否造到破坏

    前言:因为新节点的默认颜色是红色,因此:

    • 如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;

    • 如果新插入节点的双亲节点颜色为红色时,就违反了规则三:“不能存在连续的红色节点”,此时需要对红黑树分情况来讨论:

    约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点💗💗💗

    • 情况1,cur为红,p为红,g为黑,u存在且为红

    解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整

    		// 存在连续红色节点时,需要一直向上调整
    		while (parent != nullptr && parent->col == RED) // 因为插入节点是红色,如果parent也是红色(连续红节点),那么需要调整
    		{
    			Node* grandfather = parent->parent; // cur的祖父
    
    			// 找叔叔时需要判断在祖父的哪一边
    			if (grandfather->left == parent) 
    			{
    				Node* uncle = grandfather->right;
    
    				// 情况一:grandfather为黑,parent为红,uncle为红,cur为红 -- 需要调整g,p,u的颜色
    				if (uncle && uncle->col == RED)
    				{
    					parent->col = BLACK;
    					uncle->col = BLACK;
    					grandfather->col = RED;
    
    					// 调整的这颗树可能为局部子树,需要继续向上处理,祖父的父亲可能为红节点, 更新cur,parent
    					cur = grandfather;
    					parent = cur->parent;
    				}
    			else
    			{
    				Node* uncle = grandfather->left;
    
    				if (uncle != nullptr && uncle->col == RED)
    				{
    					parent->col = BLACK;
    					uncle->col = BLACK;
    					grandfather->col = RED;
    
    					cur = grandfather;
    					parent = cur->parent;
    			}
    			
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    在这里插入图片描述


    • 情况2:cur为红,p为红,g为黑,u不存在或u存在且为黑

    在这里插入图片描述

    		// 存在连续红色节点时,需要一直向上调整
    		while (parent != nullptr && parent->col == RED) // 因为插入节点是红色,如果parent也是红色(连续红节点),那么需要调整
    		{
    			Node* grandfather = parent->parent; // cur的祖父
    
    			// 找叔叔时需要判断在祖父的哪一边
    			if (grandfather->left == parent) 
    			{
    				Node* uncle = grandfather->right;
    
    				// 情况一:grandfather为黑,parent为红,uncle为红,cur为红 -- 需要调整g,p,u的颜色
    				if (uncle && uncle->col == RED)
    				{
    					parent->col = BLACK;
    					uncle->col = BLACK;
    					grandfather->col = RED;
    
    					// 调整的这颗树可能为局部子树,需要继续向上处理,祖父的父亲可能为红节点, 更新cur,parent
    					cur = grandfather;
    					parent = cur->parent;
    				}
    				// 情况二:grandfather为黑,parent为红,uncle为nullptr/黑,cur为红 -- 需要旋转+变色处理
    				else
    				{
    					// 右旋,插入节点为parent的左边,祖父为旋转点,parent为祖父的左边,uncle为祖父的右边
    					if (cur == parent->left)
    					{
    						//     g
    						//   p
    						// c
    						RotateR(grandfather);
    						// 更新颜色 -- 旋转后parent为根,根要变黑(根变黑后所有子树的路径的黑节点数量都加1) -- grandfather为根的子树,变红
    						parent->col = BLACK;
    						grandfather->col = RED;
    					}
    			}
    			else
    			{
    				Node* uncle = grandfather->left;
    
    				if (uncle != nullptr && uncle->col == RED)
    				{
    					parent->col = BLACK;
    					uncle->col = BLACK;
    					grandfather->col = RED;
    
    					cur = grandfather;
    					parent = cur->parent;
    				}
    				else
    				{
    					// 左旋,插入节点为parent的右边,祖父为旋转点,parent为祖父的右边,uncle为祖父的左边
    					// g
    					//    p
    					//      c
    					if (cur == parent->right)
    					{
    						RotateL(grandfather);
    						// 更新颜色
    						parent->col = BLACK;
    						grandfather->col = RED;
    					}
    			}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    `
    具体情况分析 + 解决方法:

    • p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,
    • p为g的右孩子,cur为p的右孩子,则进行左单旋转
    • 旋转后更新颜色,p色黑色,g变红色

    在这里插入图片描述


    1. 情况三:cur为红,p为红,u不存在或u存在且为黑

    注意:情况1是直线的情况,而情况三是折线的情况,即cur插入的方向与p指向c的方向相反

    在这里插入图片描述
    具体图 + 解决方案:

    • p为g的左孩子,u为p的右孩子或不存在,c为p的右孩子时,使用左右双旋解决,左旋用p作为旋转点,,右旋使用g为旋转点,旋转后将g颜色改为红,c改为黑

    • p为g的右孩子,u为p的左孩子或不存在,c为p的左孩子时,使用右左双旋解决,右旋用p作为旋转点,,左旋使用g为旋转点,旋转后将g颜色改为红,c改为黑

    在这里插入图片描述

    • 完整插入代码 – 包括这里的情况三
    bool insert(const pair<K, V>& kv)
    	{
    		if (root == nullptr)
    		{
    			root = new Node(kv);
    			root->col = BLACK; // 根节点为黑色
    			return true;
    		}
    
    		// 按搜索树规则插入
    		Node* cur = root;
    		Node* parent = nullptr;
    
    		while (cur != nullptr)
    		{
    			if (cur->kv.first > kv.first) {
    				parent = cur;
    				cur = cur->left;
    			}
    			else if (cur->kv.first < kv.first) {
    				parent = cur;
    				cur = cur->right;
    			}
    			else {
    				return false;
    			}
    		}
    
    		cur = new Node(kv);
    		// 如果插入节点是红色,可能破坏规则2  ---  如果插入节点是黑色,一定会破坏规则3(3很难处理)
    		cur->col = RED;
    		if (parent->kv.first > kv.first) {
    			parent->left = cur;
    		}
    		else {
    			parent->right = cur;
    		}
    		cur->parent = parent;
    
    
    		// 存在连续红色节点时,需要一直向上调整
    		while (parent != nullptr && parent->col == RED) // 因为插入节点是红色,如果parent也是红色(连续红节点),那么需要调整
    		{
    			Node* grandfather = parent->parent; // cur的祖父
    
    			// 找叔叔时需要判断在祖父的哪一边
    			if (grandfather->left == parent) 
    			{
    				Node* uncle = grandfather->right;
    
    				// 情况一:grandfather为黑,parent为红,uncle为红,cur为红 -- 需要调整g,p,u的颜色
    				if (uncle && uncle->col == RED)
    				{
    					parent->col = BLACK;
    					uncle->col = BLACK;
    					grandfather->col = RED;
    
    					// 调整的这颗树可能为局部子树,需要继续向上处理,祖父的父亲可能为红节点, 更新cur,parent
    					cur = grandfather;
    					parent = cur->parent;
    				}
    				// 情况二:grandfather为黑,parent为红,uncle为nullptr/黑,cur为红 -- 需要旋转+变色处理
    				else
    				{
    					// 右旋,插入节点为parent的左边,祖父为旋转点,parent为祖父的左边,uncle为祖父的右边
    					if (cur == parent->left)
    					{
    						//     g
    						//   p
    						// c
    						RotateR(grandfather);
    						// 更新颜色 -- 旋转后parent为根,根要变黑(根变黑后所有子树的路径的黑节点数量都加1) -- grandfather为根的子树,变红
    						parent->col = BLACK;
    						grandfather->col = RED;
    					}
    					else // 第三种情况,左右双旋,cur插入parent的右边,且parent为g的左边,,形成折线
    					{
    						//    g
    						// p
    						//    c
    						RotateL(parent);
    						RotateR(grandfather);
    
    						// 更新颜色
    						grandfather->col = RED;
    						cur->col = BLACK;
    					}
    					break;
    				}
    			}
    			else
    			{
    				Node* uncle = grandfather->left;
    
    				if (uncle != nullptr && uncle->col == RED)
    				{
    					parent->col = BLACK;
    					uncle->col = BLACK;
    					grandfather->col = RED;
    
    					cur = grandfather;
    					parent = cur->parent;
    				}
    				else
    				{
    					// 左旋,插入节点为parent的右边,祖父为旋转点,parent为祖父的右边,uncle为祖父的左边
    					// g
    					//    p
    					//      c
    					if (cur == parent->right)
    					{
    						RotateL(grandfather);
    						// 更新颜色
    						parent->col = BLACK;
    						grandfather->col = RED;
    					}
    					else // 右左双旋,cur插入parent的左边,且parent为g的右边,形成折线
    					{
    						// g
    						//    p
    						// c
    						RotateR(parent);
    						RotateL(grandfather);
    
    						// 更新颜色
    						grandfather->col = RED;
    						cur->col = BLACK;
    					}
    					break;
    				}
    			}
    		}
    		root->col = BLACK; // 调整完后,根节点可能是红,需要调整为黑
    		return true;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 左旋与右旋代码
    	// 左旋,右边高,左边矮
    	void RotateL(Node* parent)
    	{
    		Node* subR = parent->right;
    		Node* subRL = subR->left;
    
    		parent->right = subRL;
    		if (subRL != nullptr)
    			subRL->parent = parent;
    
    		Node* PpNode = parent->parent;
    
    		subR->left = parent;
    		parent->parent = subR;
    
    		if (parent == root)
    		{
    			root = subR;
    			root->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 != nullptr)
    			subLR->parent = parent;
    
    		Node* PpNode = parent->parent;
    
    		subL->right = parent;
    		parent->parent = subL;
    
    		if (parent == root)
    		{
    			root = subL;
    			root->parent = nullptr;
    		}
    		else
    		{
    			if (PpNode->left == parent) {
    				PpNode->left = subL;
    			}
    			else {
    				PpNode->right = subL;
    			}
    			subL->parent = PpNode;
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    🌭4、红黑树的删除

    其他文章
    红黑树的删除不做讲解(比插入难很多,性价比低):《算法导论》或者《STL源码剖析》

    • 这里我只实现了第一步,以搜索树规则删除节点(注意空指针访问问题)
    bool erase(const K& key)
    	{
    		Node* cur = root;
    		Node* Cparent = nullptr;
    		while (cur)
    		{
    			if (cur->kv.first > key)
    			{
    				Cparent = cur;
    				cur = cur->left;
    			}
    			else if (cur->kv.first < key)
    			{
    				Cparent = cur;
    				cur = cur->right;
    			}
    			else // 找到开始删除
    			{
    				// 删除节点存在左节点
    				if (cur->right == nullptr) 
    				{
    					// 右节点为空且没有父节点,说明只有右子树或只有一个根节点
    					if (Cparent == nullptr) 
    					{
    						root = root->left;
    						if (root !=nullptr)
    							root->parent = nullptr;
    					}
    					else
    					{
    						if (cur == Cparent->left) {
    							Cparent->left = cur->left;
    							if (cur->left)
    								cur->left->parent = Cparent;
    						}
    						else
    						{
    							Cparent->right = cur->left;
    							if (cur->left)
    								cur->left->parent = Cparent;
    						}
    					}
    				}
    				// 删除节点存在右节点
    				else if (cur->left == nullptr)
    				{
    					// 右节点为空且没有父节点,说明只有右子树或只有一个根节点
    					if (Cparent == nullptr)
    					{
    						root = root->right;
    						if (root != nullptr)
    							root->parent = nullptr;
    					}
    					else
    					{
    						if (cur == Cparent->right)
    						{
    							Cparent->right = cur->right;
    							if (cur->right)
    							cur->right->parent = Cparent;
    						}
    						else
    						{
    							Cparent->left = cur->right;
    							if (cur->right)
    								cur->right->parent = Cparent;
    						}
    					}
    				}
    				else // 删除节点存在左右节点 -- 替换数据法
    				{
    					Node* Left_Max = cur->left;
    					Node* LMparent = cur;
    
    					while (Left_Max->right)
    					{
    						LMparent = Left_Max;
    						Left_Max = Left_Max->right;
    					}
    					// 交换Cparent左子树和删除节点的值
    					std::swap(Left_Max->kv.first, cur->kv.first);
    					std::swap(Left_Max->kv.second, cur->kv.second);
    
    					// Cparent链接cur中左/右节点
    					if (LMparent->left == Left_Max)
    					{
    						LMparent->left = Left_Max->left;
    						if (cur->left)
    							Left_Max->left->parent = LMparent;
    					}
    					else
    					{
    						LMparent->right = Left_Max->left;
    						if (Left_Max->left)
    							Left_Max->left->parent = LMparent;
    					}
    					cur = Left_Max;
    				}
    
    				while (Cparent && Cparent->col == RED)
    				{
    					Node* grandfather = Cparent->parent;
    					if (grandfather && grandfather->right == Cparent)
    					{
    						Node* uncle = grandfather->left;
    						break;
    					}
    					else
    					{
    						Node* uncle = grandfather->right;
    						break;
    					}
    				}
    
    				if (root)
    					root->col = BLACK; // 删除节点可能为根 -- 删除的处理是找替代节点,只有二个节点时让root指向下一个节点
    
    				delete cur;
    				return true;
    			}
    		}
    		return false;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123

    🌮5、红黑树与AVL树的比较

    • 红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O( l o g 2 N log_2 N log2N),红黑树不追
      求绝对平衡,其只需保证最长路径不超过最短路径的2倍

    • 相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多


    🌯6、 红黑树的应用

    1. C++ STL库 – map/set、mutil_map/mutil_set
    2. Java 库
    3. linux内核
    4. 其他一些库
  • 相关阅读:
    【数据结构与算法】二分查找算法
    C++——C++入门(二)
    使用DESeq2进行转录组原始count标准化和差异分析
    RAG之微调垂域BGE的经验之谈
    JMeter学习(一)工具简单介绍
    自然语言推断-PyTorch
    嵌入式 Linux 入门 环境篇(二、安装虚拟机 — 体验 Ubuntu 22.04)
    数据安全前言技术研究联邦学习
    前端小基础
    MYSQL一次慢查询优化,不要被“索引“蒙蔽了双眼
  • 原文地址:https://blog.csdn.net/weixin_59400943/article/details/126337358