数据结构——红黑树

一、什么是红黑树

(一)红黑树的由来

由于BST树可能因大量的插入或删除操作导致树逐渐退化(最坏情况下退化成链表),从而使其相关操作的效率降低(最坏的情况下,降低到O(n))

如何能让其各个操作的效率一直维持在O(logn)呢?AVL树和红黑树便应运而生

AVL树和红黑树相较于二叉搜索树来说,在进行插入和删除操作后,会进行平衡检测,一旦发生失衡,则立即调整,使其重新变得平衡,所以AVL树一般可以称为平衡二叉搜索树(题目中若未特别说明,则默认是AVL树),红黑树也一样

(二)红黑树的基本性质

性质1:红黑树是一棵二叉搜索树(口诀:左根右)

性质2:根节点和叶子节点都必须是黑色(口诀:根叶黑)

注意:红黑树里面所提及的叶子节点不是度为 0 的节点,而是特指 NULL节点

性质3:红色节点的左右孩子都是黑色(口诀:不红红)

换句话说,红黑树中不能出现连续的两个红节点

性质4:从任意一个节点出发,到其所有可达的叶子节点的每条路径上,包含的黑色节点数量完全相同(口诀:路黑同)

(三)红黑树的核心平衡结论

任意节点到其最远叶子节点的最长路径长度,不超过最短路径长度的 2 倍;

也就是说,任意节点的左右子树高度差不超过2倍

证明:假设某条最短路径的黑色节点数为 h,根据性质4,所有路径的黑色节点数都是 h;根据性质3,红色节点不能连续出现,因此最长路径只能是黑红交替,最多包含 h 个红色节点,总路径长度为 2h

二、红黑树相关操作及代码实现

(一)红黑树的节点定义

1.有效节点

cpp 复制代码
typedef int ELEMTYPE;

// 节点颜色枚举:红色、黑色
typedef enum { RED, BLACK } ColorType;

// 红黑树有效节点结构体设计
typedef struct RBNode
{
	ELEMTYPE data; // 1.数据域:存储节点数值
	struct RBNode* leftchild; // 2.左孩子指针域
	struct RBNode* rightchild; // 3.右孩子指针域
	struct RBNode* parent; // 4.双亲指针域
	ColorType color; // 5.节点颜色属性
}RBNode;

2.辅助节点

cpp 复制代码
//红黑树辅助节点结构体设计
typedef struct RBTree
{
	RBNode* root; // 根节点指针域
}RBTree;

(二)工具函数

1.购买新节点

默认购买的新节点颜色为红色。

原因:

如果新节点为黑色:会直接改变当前路径的黑色节点总数,100%破坏性质4(路黑同),无论父节点是什么颜色,插入后都必须进行平衡修复,无任何例外情况。


如果新节点为红色:仅可能破坏性质3(不红红);如果新节点的父节点是黑色,不违反任何红黑树规则,无需进行平衡调整,直接插入成功,存在免调整场景。


但是,新节点为红色,并非一定都是免调整场景,也存在需要进行平衡调整的情况


总结:新购买的节点,一般默认颜色为红色;因为和默认黑色相比,默认红色有一定概率无需进行平衡调整,可以直接结束;而默认黑色,则一定需要进行平衡调整,因为一定会破坏性质4(路黑同)

cpp 复制代码
// 购买新节点
RBNode* BuyNode(ELEMTYPE val)
{
	RBNode* pnewnode = (RBNode*)malloc(sizeof(RBNode));
	if (NULL == pnewnode)
		exit(EXIT_FAILURE); // 内存申请失败,直接退出程序

	pnewnode->data = val;
	pnewnode->leftchild = NULL;
	pnewnode->rightchild = NULL;
	pnewnode->parent = NULL;
	pnewnode->color = RED; // 新节点默认红色

	return pnewnode;
}

2.左旋

红黑树相较于AVL树,它不需要修正结点的高度,但是需注意是否要修正双亲指针parent

cpp 复制代码
// 左旋 
RBNode* LeftRotate(RBNode* node) 
{
	// 左旋:让失衡节点node旋转到其右孩子的左孩子位置

	assert(node != NULL);

	// 申请两个指针child和grandchild分别指向对应的那个孩子和孙子
	RBNode* child = node->rightchild; // 右孩子
	RBNode* grandchild = child->leftchild; // 右孩子的左孩子

	// 默认grandchild存在
	// 如果grandchild真的存在,将它收为失衡节点node的右孩
	// 如果grandchild不存在(==NULL),则将该空节点收为失衡节点node的右孩
	node->rightchild = grandchild;
	if (grandchild != NULL)
		grandchild->parent = node;

	// 此时可以让node直接旋转下来,让child接收
	child->leftchild = node;
	node->parent = child;

	// 红黑树相较于AVL树,它不需要修正结点的高度,但是需注意是否要修正双亲指针parent

    // 返回旋转后的新的根节点
	return child;
}

3.右旋

红黑树相较于AVL树,它不需要修正结点的高度,但是需注意是否要修正双亲指针parent

cpp 复制代码
// 右旋
RBNode* RightRotate(RBNode* node)
{
    // 右旋:让失衡节点node旋转到其左孩子的右孩子位置

	assert(node != NULL);

	// 申请两个指针child和grandchild分别指向对应的孩子和孙子
	RBNode* child = node->leftchild; // 左孩子
	RBNode* grandchild = child->rightchild; // 左孩子的右孩子

	// 默认grandchild存在
	// 如果grandchild真的存在,将它收为失衡节点node的左孩
	// 如果grandchild不存在(== NULL),则将该空节点收为失衡节点node的左孩
	node->leftchild = grandchild;
	if (grandchild != NULL)
		grandchild->parent = node;

	// 此时可以让node直接旋转下来,让child来接收
	child->rightchild = node;
	node->parent = child;

	// 红黑树相较于AVL树,它不需要修正结点的高度,但是需注意是否要修正双亲指针parent

    // 返回旋转后的新的根节点	
    return child;
}

4.插入操作的调整函数

(1)普通版
cpp 复制代码
// 插入操作的调整函数
// 在辅助节点pTree->root指向的红黑树上,考虑node节点是否导致了红黑树失衡
void Insert_Adjust(RBTree* pTree, RBNode* node)
{
	// 0.断言
	assert(pTree != NULL);

    // 1.获取node节点的父节点、爷爷节点
	RBNode* father = node->parent; 
	RBNode* grandfather = father != NULL ? father->parent : NULL;

    // 2.判断node节点的父节点是否存在
	// 2.1 父节点不存在,则说明node节点根节点,根节点必须黑色
	if (NULL == father)
	{
		node->color = BLACK;
		return;
	}

	// 2.2 父节点存在,观察父节点颜色,若父节点颜色是黑色 => 无需调整,直接结束
	if (father->color == BLACK)
		return;

    // 执行至此:说明父节点存在且为红色,间接说明grandfater一定存在,因为父节点是红色不可能作为根节点(性质2:根叶黑)

	// 3.获取叔叔节点
	RBNode* uncle = grandfather->leftchild == father ? grandfather->rightchild : grandfather->leftchild;

	// 3.1 叔叔节点存在且为红色 => 叔父爷变色,然后将其爷爷节点看作新插入的节点,向上递归修复
	if (NULL != uncle && RED == uncle->color)
	{
		// 父、叔叔变黑,爷爷变红
		uncle->color = BLACK;
		father->color = BLACK;
		grandfather->color = RED;

		// 以爷爷节点为新节点,向上回溯修复上层失衡
		return Insert_Adjust(pTree, grandfather);
	}

	// 3.2 叔叔节点不存在 或者 存在但颜色是黑色 => 通过三个节点(新节点node,父节点,爷爷节点)来判断失衡类型,调用对应的旋转策略+变色操作
    // 左分支失衡:父亲是爷爷的左孩子
	if (grandfather->leftchild == father) // L()
	{
		if (father->leftchild == node) // LL
		{
            // 记录太爷爷,用于旋转后挂载上层节点
			RBNode* great_grandfather = grandfather->parent; // 太爷爷可能存在,可能不存在
            // 如果太爷爷存在,则让太爷爷来接收旋转函数的返回节点
			// 如果太爷爷不存在,也就说明原本爷爷节点就是根节点,此时旋转函数的返回值让辅助节点接收即可
			
			// 以爷爷为基准右旋
			RBNode* ptr = RightRotate(grandfather);
    
            // 处理上层挂载关系
			if (NULL == great_grandfather)
			{
				pTree->root = ptr;
				ptr->parent = NULL;
			}
			else
			{
				if (ptr->data < great_grandfather->data)
					great_grandfather->leftchild = ptr;
				else
					great_grandfather->rightchild = ptr;
				ptr->parent = great_grandfather;
			}

			//变色(找谁绕着谁转)
			father->color = BLACK;
			grandfather->color = RED;
		}
		else //LR
		{
			// 预先变色(以第二次旋转为准来找谁绕着谁转)(旋转和变色先后顺序不影响)
			grandfather->color = RED;
			node->color = BLACK;

			RBNode* great_grandfather = grandfather->parent; //太爷爷有可能存在,有可能不存在
            // 如果太爷爷存在,则让太爷爷来接收旋转函数的返回节点
			// 如果太爷爷不存在,也就说明原本爷爷节点就是根节点,此时旋转函数的返回值让辅助节点接收即可

            // 以爷爷节点为基准 执行先左旋再右旋
			grandfather->leftchild = LeftRotate(father); // 先左旋爷爷节点的左孩子,也就是父节点

			RBNode* ptr = RightRotate(grandfather); // 再右旋爷爷节点自己 
    
            // 挂载上层节点
			if (great_grandfather == NULL)
			{
				pTree->root = ptr;
				ptr->parent = NULL;
			}
			else
			{
				if (ptr->data < great_grandfather->data)
					great_grandfather->leftchild = ptr;
				else
					great_grandfather->rightchild = ptr;
				ptr->parent = great_grandfather;
			}
		}
	}

    // 右分支失衡:父亲是爷爷的右孩子
	if (grandfather->rightchild == father) // ()R
	{
		if (father->rightchild == node) //RR
		{
			RBNode* great_grandfather = grandfather->parent; //太爷爷有可能存在,有可能不存在
            // 如果太爷爷存在,则让太爷爷来接收旋转函数的返回节点
			// 如果太爷爷不存在,也就说明原本爷爷节点就是根节点,此时旋转函数的返回值让辅助节点接收即可

			// 以爷爷为基准左旋
			RBNode* ptr = LeftRotate(grandfather);

            // 挂载上层节点
			if (great_grandfather == NULL)
			{
				pTree->root = ptr;
				ptr->parent = NULL;
			}
			else
			{
				if (ptr->data < great_grandfather->data)
					great_grandfather->leftchild = ptr;
				else
					great_grandfather->rightchild = ptr;
				ptr->parent = great_grandfather;
			}

			//变色(找谁绕着谁转)
			father->color = BLACK;
			grandfather->color = RED;
		}
		else //RL
		{
			// 预先变色(以第二次旋转为准来找谁绕着谁转)(旋转和变色先后顺序不影响)
			grandfather->color = RED;
			node->color = BLACK;

			RBNode* great_grandfather = grandfather->parent; //太爷爷有可能存在,有可能不存在
            // 如果太爷爷存在,则让太爷爷来接收旋转函数的返回节点
			// 如果太爷爷不存在,也就说明原本爷爷节点就是根节点,此时旋转函数的返回值让辅助节点接收即可

			//以爷爷节点为基准 执行先右旋再左旋
			grandfather->rightchild = RightRotate(father); // 先右旋爷爷节点的右孩子,也就是父节点

			RBNode* ptr = LeftRotate(grandfather); //再左旋爷爷节点自己

			// 挂载上层节点
			if (great_grandfather == NULL)
			{
				pTree->root = ptr;
				ptr->parent = NULL;
			}
			else
			{
				if (ptr->data < great_grandfather->data)
					great_grandfather->leftchild = ptr;
				else
					great_grandfather->rightchild = ptr;
				ptr->parent = great_grandfather;
			}
		}
	}
	return;
}
(2)优化版

普通版代码旋转变色部分存在大量重复代码,优化版将重复的节点挂载 部分作为工具函数 Return_value_receive_by,大幅精简冗余代码,使结构更清晰明了

cpp 复制代码
// 让旋转函数的返回值,被正确的人来接收
void Return_value_receive_by(RBNode* ptr, RBNode* greatgrandfather, RBTree* pTree)
{
	//太爷爷不存在 => 辅助节点来接收ptr 
	if (greatgrandfather == NULL)
	{
		pTree->root = ptr;
		ptr->parent = NULL;
	}

	//太爷爷存在 => 太爷爷来接收ptr
	else
	{
		if (ptr->data < greatgrandfather->data)
			greatgrandfather->leftchild = ptr;
		else
			greatgrandfather->rightchild = ptr;
		ptr->parent = greatgrandfather;
	}
}
cpp 复制代码
// 插入操作的调整函数
// 在辅助节点pTree->root指向的红黑树上,考虑node节点是否导致了红黑树失衡
void Insert_Adjust(RBTree* pTree, RBNode* node)
{
	// 0.断言
	assert(pTree != NULL);

    // 1.获取node节点的父节点、爷爷节点
	RBNode* father = node->parent; 
	RBNode* grandfather = father != NULL ? father->parent : NULL;

    // 2.判断node节点的父节点是否存在
	// 2.1 父节点不存在,则说明node节点根节点,根节点必须黑色
	if (NULL == father)
	{
		node->color = BLACK;
		return;
	}

	// 2.2 父节点存在,观察父节点颜色,若父节点颜色是黑色 => 无需调整,直接结束
	if (father->color == BLACK)
		return;

    // 执行至此:说明父节点存在且为红色,间接说明grandfater一定存在,因为父节点是红色不可能作为根节点(性质2:根叶黑)

	// 3.获取叔叔节点
	RBNode* uncle = grandfather->leftchild == father ? grandfather->rightchild : grandfather->leftchild;

	// 3.1 叔叔节点存在且为红色 => 叔父爷变色,然后将其爷爷节点看作新插入的节点,向上递归修复
	if (NULL != uncle && RED == uncle->color)
	{
		// 父、叔叔变黑,爷爷变红
		uncle->color = BLACK;
		father->color = BLACK;
		grandfather->color = RED;

		// 以爷爷节点为新节点,向上回溯修复上层失衡
		return Insert_Adjust(pTree, grandfather);
	}

	// 3.2 叔叔节点不存在 或者 存在但颜色是黑色 => 通过三个节点(新节点node,父节点,爷爷节点)来判断失衡类型,调用对应的旋转策略+变色操作
    // 左分支失衡:父亲是爷爷的左孩子
	if (grandfather->leftchild == father) //L()
	{
		if (father->leftchild == node) //LL
		{

			// 记录太爷爷,用于旋转后挂载上层节点
			RBNode* great_grandfather = grandfather->parent; // 太爷爷可能存在,可能不存在
            // 如果太爷爷存在,则让太爷爷来接收旋转函数的返回节点
			// 如果太爷爷不存在,也就说明原本爷爷节点就是根节点,此时旋转函数的返回值让辅助节点接收即可
			
			// 以爷爷为基准右旋
			RBNode* ptr = RightRotate(grandfather);

            // 调用工具函数统一处理挂载
			Return_value_receive_by(ptr, great_grandfather, pTree);

			//变色(找谁绕着谁转)
			father->color = BLACK;
			grandfather->color = RED;
		}
		else //LR
		{
			// 预先变色(以第二次旋转为准来找谁绕着谁转)(旋转和变色先后顺序不影响)
			grandfather->color = RED;
			node->color = BLACK;

			RBNode* great_grandfather = grandfather->parent; //太爷爷有可能存在,有可能不存在
            // 如果太爷爷存在,则让太爷爷来接收旋转函数的返回节点
			// 如果太爷爷不存在,也就说明原本爷爷节点就是根节点,此时旋转函数的返回值让辅助节点接收即可

            // 以爷爷节点为基准 执行先左旋再右旋
			grandfather->leftchild = LeftRotate(father); // 先左旋爷爷节点的左孩子,也就是父节点

			RBNode* ptr = RightRotate(grandfather); // 再右旋爷爷节点自己 

            // 调用工具函数统一处理挂载    
			Return_value_receive_by(ptr, great_grandfather, pTree);
		}
	}

	// 右分支失衡:父亲是爷爷的右孩子
	if (grandfather->rightchild == father) // ()R
	{
		if (father->rightchild == node) //RR
		{
			RBNode* great_grandfather = grandfather->parent; //太爷爷有可能存在,有可能不存在
            // 如果太爷爷存在,则让太爷爷来接收旋转函数的返回节点
			// 如果太爷爷不存在,也就说明原本爷爷节点就是根节点,此时旋转函数的返回值让辅助节点接收即可

			// 以爷爷为基准左旋
			RBNode* ptr = LeftRotate(grandfather);

            // 调用工具函数统一处理挂载    
			Return_value_receive_by(ptr, great_grandfather, pTree);

			//变色(找谁绕着谁转)
			father->color = BLACK;
			grandfather->color = RED;
		}
		else //RL
		{
			// 预先变色(以第二次旋转为准来找谁绕着谁转)(旋转和变色先后顺序不影响)
			grandfather->color = RED;
			node->color = BLACK;

			RBNode* great_grandfather = grandfather->parent; //太爷爷有可能存在,有可能不存在
            // 如果太爷爷存在,则让太爷爷来接收旋转函数的返回节点
			// 如果太爷爷不存在,也就说明原本爷爷节点就是根节点,此时旋转函数的返回值让辅助节点接收即可

			//以爷爷节点为基准 执行先右旋再左旋
			grandfather->rightchild = RightRotate(father); // 先右旋爷爷节点的右孩子,也就是父节点

			RBNode* ptr = LeftRotate(grandfather); //再左旋爷爷节点自己

            // 调用工具函数统一处理挂载    
			Return_value_receive_by(ptr, great_grandfather, pTree);
		}
	}
	return;
}

5.删除操作的调整函数

cpp 复制代码
// 删除操作的调整函数
void Delete_Adjust(RBTree* pTree, RBNode* node) // node为0/1分支待删节点
{
	assert(pTree != NULL);

	// 1.申请两个指针child和father,分别指向node的孩子节点和父节点
	RBNode* father = node->parent; // 可能为NULL
	RBNode* child = node->leftchild != NULL ? node->leftchild : node->rightchild;

	// 2.处理1分支节点
	if (child != NULL) // 此时node一定是1分支结点
	{
		node->data = child->data;
		node->leftchild = node->rightchild = NULL;
		free(child);
		child = NULL;
		return;
	}

	// 3.处理0分支节点
	else
	{
		// 3.1 0分支红色节点:直接删除,无失衡无需调整
		if (node->color == RED) 
		{
			if (node == father->leftchild) father->leftchild = NULL;
			else father->rightchild = NULL;
			free(node);
			node = NULL;
			return;
		}

        // 3.2 0分支黑色节点:删除会缺失黑色高度,必然失衡,需要调整
		else 
		{
            // 根节点直接删除,无需调整
			if (father == NULL) 
			{
				pTree->root = NULL;
				free(node);
				node = NULL;
				return;
			}

			// 父节点存在,必有兄弟节点,根据兄弟节点颜色分类调整
			else
			{
                // 获取当前节点的兄弟节点
				RBNode* sibling = node == father->leftchild ? father->rightchild : father->leftchild;

				// 情况1:兄弟节点为红色
				if (sibling->color == RED)
				{
					//父兄变色:父变红、兄弟变黑
					father->color = RED;
					sibling->color = BLACK;

					//让父节点向待删除节点一侧进行单旋
					RBNode* grandfather = father->parent;
					RBNode* ptr = father->leftchild == node ? LeftRotate(father) : RightRotate(father);
					Return_value_receive_by(ptr, grandfather, pTree);
					
					// 旋转后更新兄弟节点,此时新兄弟一定为黑色
					sibling = father->leftchild == node ? father->rightchild : father->leftchild;
				}

				// 执行至此:兄弟节点一定是黑色(原生黑 / 红兄弟旋转后变黑)
				// 查找黑兄弟是否存在红色孩子
				RBNode* redchild = NULL;
				if (sibling->leftchild != NULL && sibling->color == RED) redchild = sibling->leftchild;
				else if (sibling->rightchild != NULL && sibling->color == RED) redchild = sibling->rightchild;
				else redchild = NULL;

				// 情况2:黑兄弟无红色孩子,向上传递失衡
				if (redchild == NULL)
				{
					// 兄弟变红,抵消删除缺失的黑色,黑色缺陷向上传递给父节点
					sibling->color = RED;

					// 父节点是根节点,直接结束
					if (father->parent == NULL)
					{
						if (node == father->leftchild) father->leftchild = NULL;
						else father->rightchild = NULL;
						free(node);
						node = NULL;
						return;
					}
					else
					{
                        // 父节点为红色:父变黑补足黑色高度,结束调整
						if (father->color == RED)
						{
							father->color = BLACK;
							if (node == father->leftchild) father->leftchild = NULL;
							else father->rightchild = NULL;
							free(node);
							node = NULL;
							return;
						}
                        // 父节点为黑色:缺陷继续向上递归传递
						else 
                        {
							if (node == father->leftchild) father->leftchild = NULL;
							else father->rightchild = NULL;
							free(node);
							node = NULL;
							Delete_Adjust(pTree, father);
						}
					}
				}

                // 情况3:黑兄弟存在红色孩子,旋转+变色彻底修复失衡
				else
				{
					RBNode* grandfather = father->parent;

					// 待删节点是父节点左孩子:左删场景
					if (node == father->leftchild)
					{
						// RR型:兄弟右孩子为红,单左旋修复
						if (sibling->rightchild == redchild)
						{
							sibling->color = father->color;
							father->color = BLACK;
							redchild->color = BLACK;
							RBNode* ptr = LeftRotate(father);
							Return_value_receive_by(ptr, grandfather, pTree);
						}

						// RL型:兄弟左孩子为红,先右后左双旋修复
						else
						{
							redchild->color = father->color;
							father->color = BLACK;
							sibling->leftchild = RightRotate(sibling);
							RBNode* ptr = LeftRotate(father);
							Return_value_receive_by(ptr, grandfather, pTree);
						}
					}

					// 待删节点是父节点右孩子:右删场景
					else
					{

						// LL型:兄弟左孩子为红,单右旋修复
						if (sibling->leftchild == redchild)
						{
							sibling->color = father->color;
							father->color = BLACK;
							redchild->color = BLACK;
							RBNode* ptr = RightRotate(father);
							Return_value_receive_by(ptr, grandfather, pTree);
						}

						// LR型:兄弟右孩子为红,先左后右双旋修复
						else
						{
							redchild->color = father->color;
							father->color = BLACK;
							sibling->rightchild = LeftRotate(sibling);
							RBNode* ptr = RightRotate(father);
							Return_value_receive_by(ptr, grandfather, pTree);
						}
					}

					// 移除最终待删除节点
					if (node == father->leftchild) father->leftchild = NULL;
					else father->rightchild = NULL;
					free(node);
					node = NULL;
				}
			}
		}
	}
}

(三)普通函数

1.初始化

cpp 复制代码
// 初始化
void Init_AVL(RBTree* pTree)
{
    // 断言:树结构体指针非空
	assert(pTree != NULL);
 
    // 初始化根节点为空树
	pTree->root = NULL;
}

2.插入

逻辑梳理:

1.判断是否需要插入

插入前必须判定目标值节点是否存在,若树中已存在对应值节点,无需重复插入,直接结束流程

  1. 从根节点开始遍历,借助指针 p 向下移动查找,指针 pp 实时记录 p 的上一级父节点,用于后续新节点挂载
  2. 循环终止判定:若 p 不为空且匹配到目标值,代表节点已存在,放弃插入;若 p 遍历至 NULL,代表节点不存在,允许插入新节点。

2.插入新节点

  1. 购买新节点(默认颜色是红色)
  2. 如果原本的红黑树是一个空树,则直接购买新节点并将颜色置黑,然后让辅助节点指向新节点(注意:如果原本是一颗空树,则无需提前探测val是否存在)
  3. 如果原本的红黑树不是一个空树,则然后新节点直接挂载到遍历留存的父节点 pp 的左/右子树,并完善双亲指针关系。

3.插入后失衡判定与修复

此时新节点pnewnode已经落位(若原树不是空树,则pnewnode此时插入在pp的下面)

  • 插入后未失衡情况

新节点的父节点存在且为黑色,不违反任何红黑树性质,直接结束无需修复

  • 插入后失衡情况

情况一:新节点无父节点,是整棵树的根节点,违反性质2(根叶黑)

直接将新节点从红色改为黑色即可修复

情况二:新节点的父节点存在且为红色,叔叔节点存在且为红色,违反性质3(不红红)

父、叔叔节点变黑,爷爷节点变红,将爷爷节点当作新插入节点,向上递归回溯修复上层失衡。

情况三:新节点的父节点存在且为红色,叔叔节点不存在或为黑色,违反性质3(不红红)

通过 新节点、父节点、爷爷节点 判定失衡类型(LL / RR / LR / RL),对应执行单旋/双旋操作,再配合配合变色机制修复

红黑树插入的变色机制:

如果是单旋:找旋转点和旋转中心点进行变色处理(换句话说,寻找谁绕着谁转,将这两个节点进行变色)

如果是双旋:以第二次旋转为基准,寻找谁绕着谁转,将这两个节点进行变色

cpp 复制代码
// 插入
bool Insert_RB(RBTree* pTree, ELEMTYPE val)
{
	// 0.断言
	assert(pTree != NULL);

	// 0.5 提前处理特殊情况:当前是空树,直接插入根节点并置黑
	if (pTree->root == NULL)
	{
		pTree->root = BuyNode(val);
		pTree->root->color = BLACK;

		return true;
	}
    
    // 1.从根节点开始遍历,查找插入位置、查重,如果已经存在则不用插入
	RBNode* p = pTree->root;
	
	// 1.5 申请指针pp,默认指向NULL,pp用于记录p的父节点
	RBNode* pp = NULL;

	// 2.遍历寻找空位 / 判断节点是否重复
	while (p != NULL && val != p->data)
	{
		pp = p; // 记录当前节点为父节点
    
        // 小于当前节点向左走,大于则向右走
		if (val < p->data)
			p = p->leftchild;
		else
			p = p->rightchild;
	}

	//4.当while循环结束的时候,p如果为NULL,说明真的不存在val值节点,可以插入,并且一定是插入在指针pp的下面
    // 4.1 循环结束,若p不为空说明节点已存在,无需重复插入,直接返回成功
	if (p != NULL)
		return true;
    
    // 4.2 循环结束,若p为空说明节点不存在,需要插入

	// 5.购买新节点,插入在pp的下面
	RBNode* pnewnode = BuyNode(val);
	if (val < pp->data)
		pp->leftchild = pnewnode;
	else
		pp->rightchild = pnewnode;

	pnewnode->parent = pp; // 维护双亲指针

    // 6.插入完成,检测并修复红黑树失衡
	// 无需判断是否失衡,函数内部自动适配所有场景
	Insert_Adjust(pTree, pnewnode);

	return true;
}

3.删除

逻辑梳理:

  1. 先按照BST树的逻辑查找待删除节点,找不到则无需删除
  2. 找到节点后,进一步区分 0分支 / 1分支 / 2分支 节点
    1. 如果是2分支结点,则通过狸猫换太子,将删除2分支节点问题转化为删除1分支/0分支节点问题
    2. 如果是0分支、1分支,统一交给调整函数处理,完成删除与平衡修复

删除失衡判定与修复:

cpp 复制代码
// 删除
bool Delete_RB(RBTree* pTree, ELEMTYPE val)
{
	assert(pTree != NULL);

	// 1.寻找待删除节点,如果找不到则无需删除
	RBNode* p = Search_RB(pTree->root, val);
	if (p == NULL) 
		return true;

	// 2.如果是2分支结点,通过"狸猫换太子"转化为0/1分支结点
	if (p->leftchild != NULL && p->rightchild != NULL)
	{
        // 找后继节点(右子树最左节点,一定是0/1分支)
		RBNode* cat = p->rightchild;
		while (cat->leftchild != NULL)
			cat = cat->leftchild;

        // 后继值覆盖原节点,待删节点变为后继节点
		p->data = cat->data;
		p = cat;
	}

	// 3.统一处理0分支/1分支删除 + 红黑树失衡调整
	Delete_Adjust(pTree, p);

	return true;
}

4.查找

cpp 复制代码
// 查找
RBNode* Search_RB(RBNode* root, ELEMTYPE val)
{
	// 空树直接返回NULL
	if (root == NULL)
		return NULL;

	RBNode* p = root;

	// 当前节点不为空且数值不匹配,持续遍历
	while (p != NULL && val != p->data)
	{
		// 目标值更小,向左子树查找
		if (val < p->data)
			p = p->leftchild;
		// 目标值更大,向右子树查找
		else
			p = p->rightchild;
	}

	// 跳出循环:找到节点/遍历至空节点
	return p;
}

5.打印(中序)

遍历规则详见:

数据结构------遍历二叉树_二叉树遍历-CSDN博客https://blog.csdn.net/wy_05136/article/details/163543978?spm=1001.2014.3001.5501

cpp 复制代码
// 打印(中序遍历-非递归实现) 
void Show_InOrder(RBNode* root)
{
    // 树为空直接返回
	if (root == NULL)
		return;

	std::stack<RBNode*> st;
	st.push(root);
	bool tag = true;

	while (!st.empty())
	{
        // tag为true时,持续遍历左子树入栈
		while (tag && st.top()->leftchild != NULL)
			st.push(st.top()->leftchild);

        // 访问当前栈顶节点
		RBNode* tmp = st.top();
		st.pop();
		printf("%d ", tmp->data);

        // 存在右子树则入栈,并开启新一轮左遍历
		if (tmp->rightchild != NULL)
		{
			st.push(tmp->rightchild);
			tag = true;
		}

        // 无右子树,禁止继续左遍历
		else
			tag = false;
	}
}

三、主函数测试

cpp 复制代码
int main()
{
	// 初始化
	RBTree tree;
	Init_AVL(&tree);

	// 批量插入数据
	int arr[] = { 10, 5, 15, 3, 7, 12, 18, 2, 4, 6 };
	int n = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < n; i++)
	{
		Insert_RB(&tree, arr[i]);
	}
	printf("中序遍历结果:\n");
	Show_InOrder(tree.root);
	printf("\n\n");

	// 查找
	printf("========== 查找功能测试 ==========\n");
	int findVal1 = 7;  
	int findVal2 = 99; 
	RBNode* ret1 = Search_RB(tree.root, findVal1);
	RBNode* ret2 = Search_RB(tree.root, findVal2);
	if (ret1 != NULL)
		printf("查找节点 %d 成功,节点存在\n", findVal1);
	else
		printf("查找节点 %d 失败,节点不存在\n", findVal1);

	if (ret2 != NULL)
		printf("查找节点 %d 成功,节点存在\n", findVal2);
	else
		printf("查找节点 %d 失败,节点不存在\n", findVal2);
	printf("\n");

	// 删除
	printf("========== 删除功能测试 ==========\n");
	Delete_RB(&tree, 10);  // 删除根节点(2分支,狸猫换太子)
	Delete_RB(&tree, 5);   // 删除中间分支节点
	Delete_RB(&tree, 2);   // 删除叶子节点

	printf("中序遍历结果:\n");
	Show_InOrder(tree.root);
	printf("\n");

	return 0;
}
相关推荐
码爸9 小时前
数据结构详细介绍
数据结构
CQU_JIAKE12 小时前
10.9【A】
数据结构
第25小时记录12 小时前
数据结构与算法 -第 3 章 常用算法-动态规划
数据结构·python
Logic10116 小时前
C语言/数据结构数位DP题解:平滑排列数字统计——区间内相邻位差不超过K的数字个数
c语言·数据结构·动态规划·时间复杂度·数位dp·算法题·前导零处理
wang090718 小时前
硬着头皮学数据结构和算法之Myers算法
数据结构·算法
垆边人似月.18 小时前
华为机试题 :最长递增子序列题目
数据结构·c++·算法·华为
yi0111 天前
LeetCode 219:存在重复元素 II——哈希表记录“最近一次出现的位置”
数据结构·人工智能·笔记·python·算法·leetcode·哈希表
禾小西1 天前
Redis 数据结构:快速的 Redis 有哪些慢操作?
数据结构·数据库·redis