C++----红黑树

概念

红黑树是一种自平衡二叉搜索树 ,通过节点颜色标记和旋转/重染色操作保持近似平衡,从而保证查找、插入和删除操作的时间复杂度为 O(log n)

红黑树必须满足以下 五大性质

  1. 每个节点要么是红色,要么是黑色。

  2. 根节点是黑色。

  3. 所有叶子节点(NIL 空节点)都是黑色。

  4. 如果一个节点是红色,那么它的两个子节点必须是黑色(不允许出现两个连续的红节点)。

  5. 从任意节点到其所有叶子节点的路径上,包含的黑色节点数必须相同(即"黑高"相等)。

为什么满足上面的性质,红黑树就能保证:其最长路径不会超过最短路径的两倍?

  1. 最短路径

    • 一条路径上全是黑色节点(不可能有红色节点夹杂)。

    • 因为黑高在所有路径上一致,所以这是最短的可能路径。

  2. 最长路径

    • 在黑色节点之间插入红色节点,使路径尽可能"长"。

    • 由于性质 (4),红色节点不能连续,最多只能在每个黑色节点后面跟一个红色节点。

    • 所以 最长路径的节点数 ≤ 2 × 黑色节点数

  3. 比例关系

    • 最短路径长度 = 黑高 (bh)。

    • 最长路径长度 ≤ 2 × bh。

因此,最长路径不会超过最短路径的两倍

代码实现

红黑树节点

与avl树不同,红黑树的节点不需要平衡因子来控制树的高度,而需要一个变量存储是红还是黑。其他的都是相同的,我们使用了结构体来定义红黑,除此以外,在对节点进行初始化,要对节点初始化为红,为什么呢?因为一般都是在插入时才会初始化节点。那我们想一想,插入红的方便处理还是黑的方便处理?如果是黑的就违背了性质5,处理难度比较大,甚至还会影响其他的性质。如果是红色的话,我们只考虑性质4即可,对其处理也是比较方便,使用改色和旋转即可:

cpp 复制代码
enum Color
{
	RED,
	BALCK
};

template<class K, class V>
struct RBNode
{
	RBNode(const pair<K, V>& kv = kv(),Color color = RED)
		:_left(nullptr),_right(nullptr),_parent(nullptr),
		_kv(kv),_color(color)
	{}

	RBNode* _left;
	RBNode* _right;
	RBNode* _parent;
	pair<K, V> _kv;

	Color _color;
};

定义红黑树:

cpp 复制代码
template<class K, class V>
class RBTree
{
	using node = RBNode<K,V>;
public:
	RBTree(node* root=nullptr)
		:_root(root)
	{}

	bool find(K value)
	{
		node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < value)
				cur = cur->_right;
			else if (cur->_kv.first < value)
				cur = cur->_left;
			else
				return true;
		}

		return false;
	}

	bool insert(const pair<K, V>& kv)
	{
    }
private:
	node* _root;
};

insert分析

接下来分析insert,插入的逻辑与搜索二叉树是一样的,但是插入完以后,与avl树一样,我们要对其进行检查,检查它是否符合avl树的性质,如果不满足要对其进行变色旋转处理。

cpp 复制代码
	bool insert(const pair<K, V>& kv)
	{
		node* newnode = new node(kv);
		if(!_root)
		{
			newnode->_color = BALCK;
			_root = newnode;
			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;
		}

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

		//检测是否符合二叉树 看看是不是红红相连
        ........
	}

我们分析,如果插入cur后,发现parent的颜色是红的,就需要进行处理,反之就不需要管。当parent的颜色是红色的时候,有这么两大种情况:

1.uncle存在且为红

上述,cur无论是插在parent的左右,还是在uncle的左右,处理方式都是一样的。

2.uncle不存在,或者存在为黑

uncle不存在或者为黑其实是一样的,不必纠结,因为uncle不存在,但pparent还有一个空节点,空节点在红黑树中都是黑树。所以uncle不存在是一种特例,就是最简单的情况,插入的就是cur,在这种情况下,pparent肯定也只有一个parent,如果存在uncle为黑,那么就不是红黑树了,在插入之前就有错误,所以,最简单的情况就是uncle不存在时。因此我们不再具体展示这种特殊情况的处理方法,直接展示通用的。

存在两种情况,第一种是cur在parent的方向与parent在pparent的方向一致,第二种就是不一致。

a.一致(LL,RR)

这是cur在LL的情况,当cur在RR时也是类似的。那么,当改色完成后,parent还用继续向上循环吗?是不用的,在cur不变红色之前,上面的树都是正常的,所以即使是旋转后,也维持了搜索树的性质,同时仔细观察的话,除了节点的数值发生了变化(pparent变成了parent等),其他的红黑树性质并没有发生改变。所以不需要再向上处理。

b.不一致(LR,RL)

当uncle为空时道理和a是一样的,这里画个图:

看一看扩展情况,其实都是类似的:

此时cur在LR的位置,而cur在RL的处理方式是一样的,不再赘述。当改色完成后不需要再向上处理。

insert代码

cpp 复制代码
	bool insert(const pair<K, V>& kv)
	{
		node* newnode = new node(kv);
		if(!_root)
		{
			newnode->_color = BALCK;
			_root = newnode;
			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;
		}

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

		cur = newnode;
		//检测是否符合二叉树 看看是不是红红相连
		while (parent && parent->_color==RED)
		{
			node* pparent = parent->_parent;
			if (parent == pparent->_left)
			{
				node* uncle = pparent->_right;
				if (uncle && uncle->_color == RED)//情况1 叔叔存在且为红
				{
					parent->_color = uncle->_color = BALCK;
					pparent->_color = RED;

					cur = pparent;
					parent = pparent->_parent;
				}

				else //叔叔不存在 或存在且为黑
				{
					if (cur == parent->_left)//左左
					{
						Rotate_R(pparent);
						parent->_color = BALCK;
						pparent->_color = RED;
					}
					else//左右
					{
						Rotate_L(parent);
						Rotate_R(pparent);
						cur->_color = BALCK;
						pparent->_color = RED;
					}

					break;
				}

			}
			else//parent在pparent的右边
			{
				node* uncle = pparent->_left;
				if (uncle && uncle->_color == RED)
				{
					parent->_color = uncle->_color = BALCK;
					pparent->_color = RED;

					cur = pparent;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_right)//RR
					{
						Rotate_L(pparent);
						parent->_color = BALCK;
						pparent->_color = RED;
					}
					else//RL
					{
						Rotate_R(parent);
						Rotate_L(pparent);
						cur->_color = BALCK;
						pparent->_color = RED;
					}
					break;
				}
			}
		}
		_root->_color = BALCK;

		return true;
	}
	void Rotate_R(node* parent)
	{
		node* L = parent->_left;
		node* LR = L->_right;

		parent->_left = LR;
		if (LR)
			LR->_parent = parent;

		L->_right = parent;
		node* pparent = parent->_parent;
		parent->_parent = L;
		if (parent == _root)
		{
			L->_parent = nullptr;
			_root = L;
		}
		else
		{
			L->_parent = pparent;
			if (pparent->_left == parent)
				pparent->_left = L;
			else
				pparent->_right = L;
		}
	}

	//左单旋
	void Rotate_L(node* parent)
	{
		node* R = parent->_right;
		node* RL = R->_left;
		parent->_right = RL;
		if (RL)
			RL->_parent = parent;

		R->_left = parent;
		node* pparent = parent->_parent;
		parent->_parent = R;
		if (parent == _root)
		{
			R->_parent = nullptr;
			_root = R;
		}
		else
		{
			R->_parent = pparent;
			if (pparent->_left == parent)
				pparent->_left = R;
			else
				pparent->_right = R;
		}
	}

检验是否为红黑树

要检验是否为红黑树,要满足两点:1.中序遍历有序,是二叉搜索树;2.满足红黑树的性质。这里就不再写中序遍历,请读者自行验证。只考虑满足红黑树性质。

考虑:1.根节点为黑;2.红红不能相连;3.各个路径黑点数相等。

第一点容易证明。第二点可以写一个层序遍历判断:

cpp 复制代码
enum Color { RED, BLACK };

struct Node {
    int key;
    Color color;
    Node* left;
    Node* right;
    Node(int k, Color c) : key(k), color(c), left(nullptr), right(nullptr) {}
};

bool checkNoRedRed(Node* root) {
    if (!root) return true;

    stack<Node*> st;
    st.push(root);

    while (!st.empty()) {
        Node* curr = st.top();
        st.pop();

        // 检查红红相连
        if (curr->color == RED) {
            if ((curr->left  && curr->left->color == RED) ||
                (curr->right && curr->right->color == RED)) {
                return false; // 发现红红相连
            }
        }

        // 前序:先处理当前,再压右,最后压左
        if (curr->right) st.push(curr->right);
        if (curr->left)  st.push(curr->left);
    }
    return true;
}

一般用递归来检查数量是否相等,方法是先任意找一条路径计算黑节点数量,再与其他的路径进行对比,看看数量是否一致,如果都一致说明满足,如果不一致说明不满足。为了方便,在检查黑节点数量的递归中顺便检查一下红红是否相连即可:

cpp 复制代码
	bool IsRBtree()
	{
		if (!_root)
			return true;

		if (_root->_color == RED)
		{
			cout << "违反了根节点为黑的规则" << endl;
			return false;
		}

		int blacksize = 0;
		node* cur = _root;
		while (cur)
		{
			if (cur->_color == BALCK)
				blacksize++;
			cur = cur->_left;
		}

		return _IsRBtree(_root, blacksize, 0);
	}
	bool _IsRBtree(node* root, int blacksize, int k)
	{
		int a = 0;
		if (root == nullptr)
		{
			if (blacksize != k)
			{
				cout << "违反了 每条路径黑节点数量相等的规则" << endl;
				return false;
			}
			return true;
		}

		if (root->_color == BALCK)
			k++;

		node* parent = root->_parent;
		if (parent && parent->_color == RED && root->_color == RED)
		{
			cout << "违反了 相邻的两个节点不为红色的规则" << endl;
			return false;
		}

		return _IsRBtree(root->_left, blacksize, k)
			&& _IsRBtree(root->_right, blacksize, k);
	}
相关推荐
nsjqj4 小时前
数据结构:Map 和 Set(一)
数据结构
学c语言的枫子5 小时前
数据结构——基本排序算法
数据结构·算法·排序算法
And_Ii6 小时前
LeetCode 5.最长回文字符串
数据结构·算法·leetcode
HY小海6 小时前
【C++】二叉搜索树
开发语言·数据结构·c++
m0_747266096 小时前
减治法计算数组中的零个数
数据结构·算法·leetcode
code monkey.6 小时前
【探寻C++之旅】第十五章:哈希表
数据结构·c++·哈希算法·散列表
念何架构之路6 小时前
Go语言数据结构和算法(七)字符串匹配算法
数据结构·算法·哈希算法
西阳未落8 小时前
数据结构初阶——AVL树的实现(C++)
数据结构
自学小白菜9 小时前
常见算法实现系列01 - 排序算法
数据结构·算法·排序算法