【数据结构】红黑树

目录

一、概念

二、红黑树的插入

(一)插入步骤

(二)插入的三种情况

1、叔叔存在且为红色

2、叔叔不存在/存在且为黑色(单旋)

3、叔叔不存在/存在且为黑色(双旋)

(三)插入代码

三、红黑树的平衡检测

四、整体代码


一、概念

        红黑树是对平衡二叉树的改进。平衡二叉树追求极致的平衡,因此在插入时需要大量频繁的旋转达到平衡的目。红黑树则放宽了对平衡的要求,减少了大量的旋转,虽然严格意义上红黑树的查找效率不如二叉平衡树,二叉平衡树的查找效率比红黑树高常数倍。但对于CPU来说,这点查找差距实际上没有什么影响,而红黑树插入减少了大量的旋转反而综合性能优于平衡二叉树。

        红黑树的性质:

        1、 每个结点不是红色就是黑色 ,但根节点必须是黑色;

        2、如果一个节点是红色的,则它的两个孩子结点是黑色的 ,也就是不存在连续的红色节点;

        3、 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点;

        4、 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)。 

           最优情况:全黑或每条路径都是一黑一红的满二叉树,高度logN

           最差情况:每颗子树左子树全黑,右子树一黑一红。高度2*logN。

二、红黑树的插入

        以下是本文红黑树节点的数据结构:

enum Color
{
	red,black
};
//红黑树的节点数据结构
template<class K, class V>
struct RBTreeNode
{
	pair<K, V> _data;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	Color _color;
	RBTreeNode(pair<K, V> data)
		:_data(data)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_color(red)
	{}
};

        由上述代码我们可以得知新建一个节点的颜色默认是红色,如果我们新建节点颜色为黑色,无论我们插入哪里,都会违反性质3,因此我们默认插入节点颜色为红色。

(一)插入步骤

        红黑树的插入与二叉平衡树类似,只是依靠不同的方式来保证平衡。首先按照二叉排序树的特性寻找插入节点的目标位置。插入节点后再根据其父节点以及叔叔节点的颜色进行不同的操作。

        旋转部分详见:【数据结构】平衡二叉树-优快云博客

(二)插入的三种情况

        以下三种情况都是插入后出现了连续的红色节点所以需要特殊处理,若插入节点后其父节点为黑色,则无需进行任何特殊处理。以下三种情况都是特殊处理。方便

1、叔叔存在且为红色

        插入节点后出现连续的红色节点且叔叔节点颜色也为红色,只需将父亲节点和叔叔节点的颜色染黑,将祖父节点的颜色染红即可。经过该操作以后该子树到其下任意一条路径上的黑色节点数目不变。

        需要注意的是,因为该子树的根被染红,因此进行该操作后需要 cur 指向 grandparent,继续检查上层节点是否出现连续的红色,也就是祖父节点的父节点也有可能是红色。所以情况一不仅仅是插入节点后可能出现的情况,也有可能是向上调整的过程中可能出现的情况。

2、叔叔不存在/存在且为黑色(单旋)

        这里我们假定叔叔存在且为黑色(实际叔叔存不存在都不影响操作),首先需要说明的是情况二只有可能出现在情况一向上调整的过程中,不可能出现在刚插入节点的过程中。若刚插入就满足情况二,说明在插入前该树就已经不满足红黑树的性质了。

        针对本情况,首先对父节点进行右旋,同时将父节点染黑并将祖父节点染红。经过此操作之后,单论黑色节点的数量的话,该子树到任意路径上黑色节点的数量与旋转前保持一致并且旋转后没有出现连续的红色节点。仍保持红黑树的特性。

        以上其实只是连续红色节点出现在左子树上,若连续红色节点出现在右子树时与上述操作类似,进行左旋并对父节点和祖父节点进行染色即可,这里便不进行赘述了。经过以上操作后子树的根节点为黑色,因此无需向上继续检查平衡因子异常了。

3、叔叔不存在/存在且为黑色(双旋)

        这里我们假定叔叔存在且为黑色(实际叔叔存不存在都不影响操作),与情况二类似,情况三只有可能出现在情况一向上调整的过程中,不可能出现在刚插入节点的过程中。若刚插入就满足情况三,说明在插入前该树就已经不满足红黑树的性质了。

         针对本情况,首先对父节点进行左旋,再对祖父节点进行右旋,并将祖父节点染红和插入节点染黑。经过此操作之后,单论黑色节点的数量的话,该子树到任意路径上黑色节点的数量与旋转前保持一致并且旋转后没有出现连续的红色节点。仍保持红黑树的特性。

         以上其实只是连续红色节点出现在左子树上,若连续红色节点出现在右子树时与上述操作类似,进行右旋和左旋并对祖父节点和插入节点进行染色即可,这里便不进行赘述了。经过以上操作后子树的根节点为黑色,因此无需向上继续检查平衡因子异常了。

(三)插入代码

        总结下三种情况,实际只有第一种情况执行后需要向上检查平衡因子异常,第二种第三种情况执行后无需向上检查平衡因子异常。

bool Insert(const pair<K, V>& data)
{
    //如果根节点为空直接插入即可
	if (_root == nullptr)
	{
		_root = new Node(data);
		_root->_color = black;
		return true;
	}

	Node* parent = nullptr;
	Node* cur = _root;

    //寻找插入节点目标位置
	while (cur)
	{
		if (cur->_data.first < data.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if(cur->_data.first > data.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}

    //插入节点
	cur = new Node(data);
	if (parent->_data.first < data.first)
		parent->_right = cur;
	else
		parent->_left = cur;
	cur->_parent = parent;

    //处理出现连续红色节点
	while (parent && parent->_color == red)
	{
		Node* grandparent = parent->_parent;
		Node* uncle = nullptr;
		if (grandparent == nullptr)
			break;
		if (parent == grandparent->_left)
			uncle = grandparent->_right;
		else
			uncle = grandparent->_left;
        
        //情况一
		if (uncle && uncle->_color == red)
		{
			parent->_color = uncle->_color = black;
			grandparent->_color = red;
			cur = grandparent;
			parent = cur->_parent;
		}
		else
		{    
            //情况二
			if (grandparent->_right == parent && parent->_right == cur)
			{
				RotateL(grandparent);
				parent->_color = black;
				grandparent->_color = red;

			}
			else if (grandparent->_left == parent && parent->_left == cur)
			{
				RotateR(grandparent);
				parent->_color = black;
				grandparent->_color = red;
			}
            //情况三
			else if (grandparent->_right == parent && parent->_left == cur)
			{
				RotateR(parent);
				RotateL(grandparent);
				cur->_color = black;
				grandparent->_color = red;
			}
			else if (grandparent->_left == parent && parent->_right == cur)
			{
				RotateL(parent);
				RotateR(grandparent);
				cur->_color = black;
				grandparent->_color = red;
			}
			else
				assert(false);
			break;
		}
        //无论如何根节点必须染黑
		_root->_color = black;
	}
	return true;
}

三、红黑树的平衡检测

        相对于平衡二叉树的严格检查平衡,红黑树的检查要宽松点。首先要检查根节点颜色是否为黑色,再检查是否存在连续的红色节点,最后再检查各个路径的黑色节点数目是否一致。

//判断是否为红黑树
bool isRBTree()
{
    //检查根节点
	if (_root->_color == red)
		return false;

	Node* cur = _root;
	int ref = 0;
    //获取最左路径下的黑色节点用于参考
	while (cur)
	{
		if (cur->_color == black)
			++ref;
		cur = cur->_left;
	}
	return check(_root, 0, ref);
}
bool check(Node* t, int num, int ref)
{
	if (t == nullptr)
	{
        //判断各个路径的黑色节点数目是否相同
		if (num != ref)
		{
			cout << "黑色节点数目异常" << endl;
			return false;
		}
		return true;
	}
    //检查是否出现了连续的红色节点
	if (t->_color == red && t->_parent->_color == red)
	{
		cout << "出现连续红色节点" << endl;
		return false;
	}
	if (t->_color == black)
		num++;
	return check(t->_left, num, ref) && check(t->_right, num, ref);
}

四、整体代码

#pragma once
#include <iostream>
#include <cassert>
using namespace std;
enum Color
{
	red,black
};
template<class K, class V>
struct RBTreeNode
{
	pair<K, V> _data;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	Color _color;
	RBTreeNode(pair<K, V> data)
		:_data(data)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_color(red)
	{}
};

template<class K, class V>
class RBTree
{
public:
	typedef RBTreeNode<K, V> Node;
	RBTree()
		:_root(nullptr)
	{}//中序遍历
	void Inorder()
	{
		_Inorder(_root);
	}
	// 左单旋
	void RotateL(Node* pParent)
	{
		Node* parent = pParent;
		Node* pNode = parent->_parent;
		Node* subR = parent->_right;

		parent->_right = subR->_left;
		if (subR->_left)
			subR->_left->_parent = parent;

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

		if (pNode == nullptr)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (pNode->_left == parent)
			{
				pNode->_left = subR;
			}
			else
			{
				pNode->_right = subR;
			}
			subR->_parent = pNode;
		}
	}
	// 右单旋
	void RotateR(Node* pParent)
	{
		Node* parent = pParent;
		Node* pNode = parent->_parent;
		Node* subL = parent->_left;

		parent->_left = subL->_right;
		if (subL->_right)
			subL->_right->_parent = parent;

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

		if (pNode == nullptr)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (pNode->_left == parent)
			{
				pNode->_left = subL;
			}
			else
			{
				pNode->_right = subL;
			}
			subL->_parent = pNode;
		}
	}
	//插入节点
	bool Insert(const pair<K, V>& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_color = black;
			return true;
		}

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

		cur = new Node(data);
		if (parent->_data.first < data.first)
			parent->_right = cur;
		else
			parent->_left = cur;
		cur->_parent = parent;

		while (parent && parent->_color == red)
		{
			Node* grandparent = parent->_parent;
			Node* uncle = nullptr;
			if (grandparent == nullptr)
				break;
			if (parent == grandparent->_left)
				uncle = grandparent->_right;
			else
				uncle = grandparent->_left;

			if (uncle && uncle->_color == red)
			{
				parent->_color = uncle->_color = black;
				grandparent->_color = red;
				cur = grandparent;
				parent = cur->_parent;
			}
			else
			{
				if (grandparent->_right == parent && parent->_right == cur)
				{
					RotateL(grandparent);
					parent->_color = black;
					grandparent->_color = red;

				}
				else if (grandparent->_left == parent && parent->_left == cur)
				{
					RotateR(grandparent);
					parent->_color = black;
					grandparent->_color = red;
				}
				else if (grandparent->_right == parent && parent->_left == cur)
				{
					RotateR(parent);
					RotateL(grandparent);
					cur->_color = black;
					grandparent->_color = red;
				}
				else if (grandparent->_left == parent && parent->_right == cur)
				{
					RotateL(parent);
					RotateR(grandparent);
					cur->_color = black;
					grandparent->_color = red;
				}
				else
					assert(false);
				break;
			}
			_root->_color = black;
		}
		return true;
	}
	//判断是否为红黑树
	bool isRBTree()
	{
		if (_root->_color == red)
			return false;

		Node* cur = _root;
		int ref = 0;
		while (cur)
		{
			if (cur->_color == black)
				++ref;
			cur = cur->_left;
		}
		return check(_root, 0, ref);
	}
private:
	bool check(Node* t, int num, int ref)
	{
		if (t == nullptr)
		{
			if (num != ref)
			{
				cout << "黑色节点数目异常" << endl;
				return false;
			}
			return true;
		}

		if (t->_color == red && t->_parent->_color == red)
		{
			cout << "出现连续红色节点" << endl;
			return false;
		}
		if (t->_color == black)
			num++;
		return check(t->_left, num, ref) && check(t->_right, num, ref);
	}
	//中序遍历
	void _Inorder(Node* root)
	{
		if (root == nullptr)
			return;
		_Inorder(root->_left);
		cout << root->_data.first << " ";
		_Inorder(root->_right);
	}
	Node* _root;
};
### 红黑树数据结构介绍 红黑树是一种自平衡二叉查找树,在实际应用中非常常见,尤其在标准模板库(STL)中的`map`和`set`容器底层实现上得到了广泛应用[^1]。 #### 定义与特性 红黑树具有以下性质: - 每个节点要么是红色,要么是黑色。 - 根节点总是黑色。 - 所有叶子节点都是黑色的NIL节点(表示不存在的实际对象)。 - 如果一个内部节点是红色,则其两个子节点都必须是黑色。(即不允许存在相邻的红色节点) - 对于任意节点而言,从该节点到其可达叶子节点的所有路径均包含相同数量的黑色节点。 这些属性共同作用使得红黑树能够在最坏情况下保持O(log n)的时间复杂度完成插入、删除以及查询操作。 #### 实现细节 关于红黑树的具体实现较为复杂,尤其是旋转调整部分。这里给出一段简化版的Python代码用于展示基本框架: ```python class Node: def __init__(self, item=None): self.item = item self.parent = None self.left = None self.right = None self.color = 'red' # 新加入结点默认为红色 class RedBlackTree: def __init__(self): self.TNULL = Node() self.root = self.TNULL def insert(self, key): ... def delete_node(self, node): ... def left_rotate(self, x): ... def right_rotate(self, y): ... ``` 上述代码仅展示了类定义的一部分;完整的逻辑还包括左旋/右旋方法、插入修正函数等更多内容[^4]。 #### 应用场景 由于良好的时间复杂性和相对简单的维护成本,红黑树被广泛应用于各种编程语言的标准库之中。例如Java集合框架里的`TreeMap`就采用了类似的机制来存储键值对[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值