红黑树的实现

红黑树

前言

前面学习了AVL树, 它是一棵绝对平衡的二叉搜索树,查找的效率很高,结构完美严格,但是一旦对AVL树做插入操作就可能会引发旋转,使性能降低。所以提出了另一种实现平衡二叉搜索树的思路,即红黑树。

1. 红黑树的概念及性质

1.1 红黑树的概念

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的。

在这里插入图片描述

1.2 红黑树的性质

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的 — 不能出现连续的红色节点
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点 — 每条路径上都有相同数量的黑色节点
  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

满足上面的性质,最短路径是全是黑色节点,如下图的红黑树,最左路径即最短路径有两个黑色节点,那么要满足每条路径上都有相同数量的黑色节点,所以其他路径上黑色节点与最短路径黑色节点数量相同,再添加一些红色节点,最长路径是一红一黑的相间路径,下图中其最长路径中节点个数是最短路径节点个数的两倍。由此可得: 红黑树最长路径中节点个数不会超过最短路径节点个数的两倍。

在这里插入图片描述

最短路径: 全是黑色节点

最长路径: 一个黑色节点一个红色节点,红黑相间

由此也引入两种情况:

  1. 最好情况: 左右平衡,全部是黑色节点或每条路径都是一黑一红相间,此时接近满二叉树,假设全部黑色节点有N个,红色节点数量不会超过黑色节点数量,此时整棵树的节点数量保持在[N, 2N]之间,平均下来路径高度为logN
  2. 最坏情况: 左右极其不平衡,左子树全黑,且右子树一黑一红。那么此时最长路径为2* logN

最好情况与最坏情况对于计算机来说相差并不大,比如有10亿个节点,AVL树最多查找30次左右,红黑树最多查找60次左右,两者虽然相差二倍,但在往树上插入节点时,AVL可能会引发大量的旋转使性能降低。比如上面形状的那棵树,AVL树时一定旋转,红黑树不一定旋转。

2. 红黑树的结构

2.1 红黑树节点的定义

相比于AVL树,红黑树不用定义平衡因子,但是想要表示节点的颜色,所以可以定义一个枚举类型表示节点颜色,还是向AVL树一样定义成三叉链的结构,方便后续找父节点来修改颜色及旋转。

//节点的颜色
enum Colour
{
	RED,
	BLACK,
};
template<class K, class V>
struct RBTreeNode	//三叉链
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	Colour _col;

	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(RED)       //默认颜色给红色
	{}
};

2.2 红黑树的结构

//节点的颜色
enum Colour
{
	RED,
	BLACK,
};
template<class K, class V>
struct RBTreeNode	//三叉链
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	Colour _col;

	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(RED)  //默认颜色给红色,假设是黑色,插入此节点会使这条路径黑节点数量变,其他路径也要跟着变
	{}
};
template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	//一系列操作
private:
	Node* _root = nullptr;
};

当然还有另一种红黑树的实现方式:

红黑树的实现中增加一个头结点, 因为跟节点必须为黑色,为了与根节点进行区分,将头结点给成黑色,并且让头结点的 pParent 域指向红黑树的根节点,pLeft域指向红黑树中最小的节点,_pRight域指向红黑树中最大的节点。

在这里插入图片描述

3. 红黑树的操作

3.1 红黑树的查找

与搜索二叉树查找思路完全类似,就是注意这里是用key值来查找

Node *Find(const K &key)
{
    Node *cur = _root;

    while (cur)
    {
        if (cur->_kv.first < key)
        {
            cur = cur->_right;
        }
        else if (cur->_kv.first > key)
        {
            cur = cur->_left;
        }
        else
        {
            return cur;
        }
    }
    return nullptr;
}

3.2 红黑树的插入

红黑树的删除比较复杂,就不实现了

插入整体的大框架与AVL树相似,还是先找到要插入的位置再去链接的过程,重点是后续处理红黑树颜色的过程

bool Insert(const pair<K, V> &kv)
{
    // 第一次插入
    if (_root == nullptr)
    {
        _root = new Node(kv);
        _root->_col = BLACK; // 根节点给黑色
        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; // 该元素已经在树中存在了, 无法插入
        }
    }

    // 链接
    cur = new Node(kv);
    if (parent->_kv.first > kv.first)
    {
        parent->_left = cur;
    }
    else
    {
        parent->_right = cur;
    }
    cur->_parent = parent;
    
    //接下来需要处理红黑树的颜色
    //...
}
处理红黑树颜色的过程(重点)

此问题的核心: 寻找叔叔节点, 总体上分为3种情况:

情况1: 只变色

在这里插入图片描述

情况2: 变色 + 单旋

在这里插入图片描述

情况3: 变色 + 双旋

在这里插入图片描述

处理颜色的代码

这里颜色处理要结合之前AVL树中的旋转,同时需要将旋转函数中平衡因子的更新去掉

while (parent && parent->_col == RED)
{
    Node *grandfather = parent->_parent;

    // 找叔叔  =>  看父亲在祖父的哪边
    if (grandfather->_left == parent)
    {
        Node *uncle = grandfather->_right;

        // 3种情况

        // 情况1: u存在且为红, 变色处理, 并继续向上处理
        // 变色: p,u变黑, g变红
        if (uncle && uncle->_col == RED)
        {
            parent->_col = BLACK;
            uncle->_col = BLACK;
            grandfather->_col = RED;

            // 继续向上调整
            cur = grandfather;
            parent = cur->_parent;
        }
        else // 情况2+3: u不存在/u存在且为黑, 旋转 + 变色
        {
            //     g
            //   p   u
            // c
            if (cur == parent->_left)
            {
                RotateR(grandfather);
                parent->_col = BLACK;
                grandfather->_col = RED;
            }
            else
            {
                //     g
                //   p   u
                //     c
                RotateL(parent);
                RotateR(grandfather);
                cur->_col = BLACK;
                grandfather->_col = RED;
            }

            break;
        }
    }
    else //(grandfather->_right == parent)
    {
        Node *uncle = grandfather->_left;

        // 3种情况

        // 情况1: u存在且为红, 变色处理, 并继续向上处理
        // 变色: p,u变黑, g变红
        if (uncle && uncle->_col == RED)
        {
            parent->_col = BLACK;
            uncle->_col = BLACK;
            grandfather->_col = RED;

            // 继续向上调整
            cur = grandfather;
            parent = cur->_parent;
        }
        else // 情况2+3: u不存在/u存在且为黑, 旋转 + 变色
        {
            //     g
            //   u   p
            //         c
            if (cur == parent->_right)
            {
                RotateL(grandfather);
                parent->_col = BLACK;
                grandfather->_col = RED;
            }
            else
            {
                //     g
                //   u   p
                //     c
                RotateR(parent);
                RotateL(grandfather);
                cur->_col = BLACK;
                grandfather->_col = RED;
            }
            break;
        }
    }
}
_root->_col = BLACK;   //根节点是黑色的

3.3 红黑树的验证

红黑树的验证分为两步:

  • 验证其为二叉搜索树

    • 若中序遍历可得到一个有序序列,就说明为二叉搜索树
  • 验证其是否满足红黑树的性质

重点实现验证满足红黑树的性质:

  1. 检查根节点
  2. DFS检查黑色节点数量,同时用一个基准值来记录最左路径的黑色节点数量,各个路径与之比较黑色节点数量
  3. 检查不能存在连续的红色节点(反向检查)
bool IsBalance() // 重点检查规则
{
    // 先检查根节点
    if (_root && _root->_col == RED)
    {
        cout << "根节点颜色是红色" << endl;
        return false;
    }

    int benchmark = 0; // 基准值
    Node *cur = _root;
    while (cur) // 走最左路径
    {
        if (cur->_col == BLACK)
            ++benchmark;

        cur = cur->_left;
    }
    // 连续红色节点
    return _Check(_root, 0, benchmark);
}

bool _Check(Node *root, int blackNum, int benchmark) // 基准值
{
    if (root == nullptr) // 空树也是红黑树
    {
        if (benchmark != blackNum)
        {
            cout << "某条路径黑色节点的数量不相等" << endl;
            return false;
        }
        return true;
    }

    // DFS检查黑色节点数量
    if (root->_col == BLACK)
    {
        ++blackNum;
    }

    // 反向检查  ---> 红色节点不能连续
    if (root->_col == RED && root->_parent && root->_parent->_col == RED)
    {
        cout << "存在连续的红色节点" << endl;
        return false;
    }
    return _Check(root->_left, blackNum, benchmark) && _Check(root->_right, blackNum, benchmark);
}

4. 红黑树完整代码

#include<iostream>
#include<utility>
#include<assert.h>
#include<stdlib.h>
using namespace std;


//节点的颜色
enum Colour
{
	RED,
	BLACK,
};


template<class K, class V>
struct RBTreeNode	//三叉链
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	Colour _col;

	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(RED)       //默认颜色给红色
	{}
};



template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	~RBTree()
	{
		_Destroy(_root);
		_root = nullptr;
	}


	Node* Find(const K& key)
	{
		Node* cur = _root;

		while (cur)
		{
			if (cur->_kv.first < key)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first >key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;      
			}
		}

		return nullptr;
	}



	bool Insert(const pair<K, V>& kv)
	{
		//第一次插入
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;         //根节点给黑色
			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;       //该元素已经在树中存在了, 无法插入
			}

		}


		//链接
		cur = new Node(kv);
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;


		//处理红黑树颜色
		while (parent && parent->_col==RED)
		{
			Node* grandfather = parent->_parent;

			//找叔叔  =>  看父亲在祖父的哪边
			if (grandfather->_left == parent)   
			{
				Node* uncle = grandfather->_right;

				//3种情况

				//情况1: u存在且为红, 变色处理, 并继续向上处理
				//变色: p,u变黑, g变红
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续向上调整
					cur=grandfather;
					parent = cur->_parent;
				}
				else    //情况2+3: u不存在/u存在且为黑, 旋转 + 变色
				{
					//     g
					//   p   u
					// c
					if(cur==parent->_left)
					{
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
					//     g
					//   p   u
					//     c
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
			else    //(grandfather->_right == parent)
			{
				Node* uncle = grandfather->_left;

				//3种情况

				//情况1: u存在且为红, 变色处理, 并继续向上处理
				//变色: p,u变黑, g变红
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续向上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else      //情况2+3: u不存在/u存在且为黑, 旋转 + 变色
				{
					//     g
					//   u   p
					//         c
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//     g
						//   u   p
	                    //     c

						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
		}
		_root->_col = BLACK;   //根节点是黑色的

		return true;
	}

	void Inorder()
	{
		_Inorder(_root);
	}

	int Height()
	{
		return _Height(_root);
	}

	bool IsBalance()    //重点检查规则
	{
		//先检查根节点
		if (_root && _root->_col == RED)
		{
			cout << "根节点颜色是红色" << endl;
			return false;
		}

		int benchmark = 0;       //基准值
		Node* cur = _root;
		while(cur)               //走最左路径
		{
			if (cur->_col == BLACK)
				++benchmark;

			cur = cur->_left;
		}

		//连续红色节点
		return _Check(_root, 0,benchmark);
	}


private:

	//左单旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		Node* ppnode = parent->_parent;

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


		if (ppnode == nullptr)				//parent本身就是根
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else								//parent只是一棵子树
		{
			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)
			subLR->_parent = parent;

		Node* ppnode = parent->_parent;

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


		if (ppnode == nullptr)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subL;
			}
			else
			{
				ppnode->_right = subL;
			}
			subL->_parent = ppnode;
		}
	}


	int _Height(Node* root)
	{
		if (root == nullptr)
			return 0;

		int leftH = _Height(root->_left);
		int rightH = _Height(root->_right);

		return leftH > rightH ? leftH + 1 : rightH + 1;
	}


	bool _Check(Node* root, int blackNum, int benchmark)  //基准值
	{
		if (root == nullptr)    //空树也是红黑树
		{
			if (benchmark != blackNum)
			{
				cout << "某条路径黑色节点的数量不相等" << endl;
				return false;
			}
			return true;
		}

		//DFS检查黑色节点数量
		if (root->_col == BLACK)
		{
			++blackNum;
		}

		//反向检查  ---> 红色节点不能连续
		if (root->_col == RED
			&& root->_parent
			&& root->_parent->_col == RED)
		{
			cout << "存在连续的红色节点" << endl;
			return false;
		}

		return _Check(root->_left,  blackNum,  benchmark)
			&& _Check(root->_right, blackNum,  benchmark);
	}

	void _Destroy(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_Destroy(root->_left);
		_Destroy(root->_right);
		delete root;
	}

	void _Inorder(Node* root)
	{
		if (root == nullptr)
			return;

		_Inorder(root->_left);
		cout << root->_kv.first << " ";
		_Inorder(root->_right);
	}

	Node* _root = nullptr;
};

5. 红黑树与AVL树的比较

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O( l o g 2 N log_2 N log2N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值