红黑树详解

  • 基础知识

红黑树是一种二叉搜索树,是AVL树的改进版。
红黑树的生长是自底向上的,跟2-3树类似
为啥引入红黑树:
1、二插搜索树会出现退化成链表的情况,其时间复杂度为O(n)
2、为防止二叉搜索树会退化成链表,引入了对左右子树的高度差有严格显示的AVL树。
    AVL树的缺点:
    因为对左右子树的高度差有严格的规定,所以其在插入和删除时会出现旋转,导致性能下降。
这才有了对平衡概念不是很严格的红黑树

红黑树与AVL树的比较
1. AVL树的时间复杂度优于红黑树,但是对于现在的计算机,这种差别可以忽略
2. 红黑树的插入删除比AVL树更便于控制操作。
3. 红黑树整体性能略优于AVL树。(红黑树旋转情况少于AVL树)。这点是非常重要的
4. 如果是在查询很多增删少的情况下 AVL 树还是优于红黑树的,如果增删比较频繁,那红黑树绝对是完美的一种选择

红黑树的特性:
1. 根节点是【黑色】
2. 每个节点是【黑色】或者是【红色】
3. 【红色】节点的两个子节点一定都是【黑色】
4. 每个叶子节点(NIL)都是【黑色】
5. 任意一个节点到叶子节点的路径上所包含的【黑色】节点的数量是相同的---这个也称之为【黑色完美平衡】

  • 插入

下图中插入节点为I,父节点为P,爷爷节点为PP,叔叔节点为U。

1、红黑树为null,则新插入节点作为根节点,变色为黑色

2、插入节点的父节点为黑色,直接插入即可

3、插入节点的父节点为红色

    3.1插入节点的父节点为插入节点的爷爷节点的左孩子
        3.1.1插入节点的叔叔节点存在,且为红色
             将新插入节点的父节点和其叔叔节点设置为黑色即可,爷爷节点设置为红色,将爷爷节点设置为当前节点

如果pp为根节点,则其仍未黑色,此时为增加黑色的格式
        3.1.2 叔叔节点不存在(其实叔叔节点不可能为黑色,因为在新插入节点之前,父节点是红色,破坏了黑色平衡,故叔叔节点一定是不存在的)
        (1)插入节点为其父节点的左孩子
                将父节点设置为黑色,爷爷节点设置为红色,对其爷爷节点进行右旋,当前节点设置为P


        (2)插入节点为其父节点的右孩子
            先对父节点进行左旋,将插入节点设置为黑色,爷爷节点设置为红色,然后对其爷爷节点进行右旋,I设置为当前点

    3.2插入节点的父节点为插入节点的爷爷节点的右孩子
        3.2.1 插入节点的叔叔节点存在,且为红色
            将新插入节点的父节点和其叔叔节点设置为黑色即可,爷爷节点设置为红色,将爷爷节点设置为当前节点


        3.2.2 叔叔节点不存在(其实叔叔节点不可能为黑色,因为在新插入节点之前,父节点是红色,破坏了黑色平衡,故叔叔节点一定是不存在的)
        (1)插入节点为其父节点的右孩子
            将父节点设置为黑色,爷爷节点设置为红色,对其爷爷节点进行左旋


        (2)插入节点为其父节点的左孩子
            先对其父节点进行右旋,将插入节点设置为黑色,爷爷节点设置为红色,然后对其爷爷节点进行左旋

 

//RBTree.h
#pragma once
/*
红黑树
是一种二叉搜索树,是AVL树的改进版。
红黑树的生长是自底向上的,跟2-3树类似
为啥引入红黑树:
1、二插搜索树会出现退化成链表的情况,其时间复杂度为O(n)
2、为防止二叉搜索树会退化成链表,引入了对左右子树的高度差有严格显示的AVL树。
	AVL树的缺点:
	因为对左右子树的高度差有严格的规定,所以其在插入和删除时会出现旋转,导致性能下降。
这才有了对平衡概念不是很严格的红黑树

红黑树与AVL树的比较
1. AVL树的时间复杂度优于红黑树,但是对于现在的计算机,这种差别可以忽略
2. 红黑树的插入删除比AVL树更便于控制操作。
3. 红黑树整体性能略优于AVL树。(红黑树旋转情况少于AVL树)。这点是非常重要的
4. 如果是在查询很多增删少的情况下 AVL 树还是优于红黑树的,如果增删比较频繁,那红黑树绝对是完美的一种选择

红黑树的特性:
1. 根节点是【黑色】
2. 每个节点是【黑色】或者是【红色】
3. 【红色】节点的两个子节点一定都是【黑色】
4. 每个叶子节点(NIL)都是【黑色】
5. 任意一个节点到叶子节点的路径上所包含的【黑色】节点的数量是相同的---这个也称之为【黑色完美平衡】
*/

#include <iomanip>
#include <iostream>

enum RBTreeColor{RED, BLACK};

template <typename T>
struct RBTreeNode
{
	RBTreeColor color;
	RBTreeNode *pLeft;
	RBTreeNode *pRight;
	RBTreeNode *pParent;//会出现找父节点变色或者旋转的情况
	T data;

	RBTreeNode(RBTreeColor c, RBTreeNode *left, RBTreeNode *right, RBTreeNode *parent, T data) :
		color(c), pLeft(left), pRight(right), pParent(parent), data(data) {}
};

template <typename T>
class RBTree
{
public:
	RBTree()
	{
		pRoot = nullptr;
	}
	~RBTree()
	{
		Destroy();
	}

	//增
	void Insert(T data);
	//删
	void Delete();
	//查

	//遍历
	void PreTravel();

	//销毁
	void Destroy();

	//打印
	void Print();

private:
	void InsertNode(RBTreeNode<T> *&pNode, T data);//插入
	void LeftRotate(RBTreeNode<T> *&pNode);//左旋
	void RightRotate(RBTreeNode<T> *&pNode);//右旋
	void InsertNodeAmend(RBTreeNode<T> *&pNode, RBTreeNode<T> *pNewNode);//修正为一颗红黑树
	void Print(RBTreeNode<T> *pNode, T data, int dirction);//data表示节点值, direction表示左节点(-1)还是右节点(1)

	void PreTravel(RBTreeNode<T> *pNode);//前序遍历

	void Destroy(RBTreeNode<T> *pNode);

	RBTreeNode<T> *pRoot;

	inline void SetBlack(RBTreeNode<T> *pNode)
	{
		pNode->color = BLACK;
	}

	inline void SetRed(RBTreeNode<T> *pNode)
	{
		pNode->color = RED;
	}
};

template<typename T>
void RBTree<T>::Insert(T data)
{
	InsertNode(pRoot, data);
}

template<typename T>
void RBTree<T>::PreTravel()
{
	PreTravel(pRoot);
}

template<typename T>
void RBTree<T>::Destroy()
{
	Destroy(pRoot);
}




template<typename T>
void RBTree<T>::Print()
{
	Print(pRoot, pRoot->data, 0);
}

template<typename T>
void RBTree<T>::InsertNode(RBTreeNode<T>* &pNode, T data)
{
	//根节点 设置为黑
	if (pNode == nullptr)
	{
		pNode = new RBTreeNode<T>(BLACK, nullptr, nullptr, nullptr, data);
		return;
	}
	RBTreeNode<T> *pNodeParent = nullptr;
	RBTreeNode<T> *pTmp = pNode;

	//遍历查找,找到待插入位置的父节点处
	while (pTmp != nullptr)
	{
		pNodeParent = pTmp;
		if (pTmp->data < data)
		{
			pTmp = pTmp->pRight;
		}
		else
		{
			pTmp = pTmp->pLeft;
		}
	}
	RBTreeNode<T> *pNew = new RBTreeNode<T>(RED, nullptr, nullptr, nullptr, data);
	pNew->pParent = pNodeParent;
	if (pNodeParent != nullptr)
	{
		if (pNodeParent->data > pNew->data)
			pNodeParent->pLeft = pNew;
		else
			pNodeParent->pRight = pNew;
	}	

	InsertNodeAmend(pNode, pNew);
}

/*
旋转步骤:
1、pNode右孩子的左子树插入pNode的右子树上,如果pNodeRightChild的左子树不为null,则其父节点为pNode
2、pNode的父节点设为pNodeRightChild的父节点
3、判断pNode的父节点是否为null,为null则说明pNode为根节点,应将其右节点作为pRoot
不为null,则应判断pNode是左子树还是右子树,将pNodeRightChild作为相应的子树
4、pNode作为其右孩子的左子树,pNode的父节点指向pNodeRightChild
5、将pNodeRightChild设为当前节点pNode
*/
template<typename T>
void RBTree<T>::LeftRotate(RBTreeNode<T>*& pNode)
{
	RBTreeNode<T> *pNodeRightChild = pNode->pRight;
	pNode->pRight = pNodeRightChild->pLeft;

	if (pNodeRightChild->pLeft != nullptr)
	{
		pNodeRightChild->pLeft->pParent = pNode;
	}
	//pNode的父节点设为pNodeRightChild的父节点
	pNodeRightChild->pParent = pNode->pParent;

	//如果pNode的父节点为null,则将其右节点设为根节点
	if (pNode->pParent == nullptr)
	{
		pRoot = pNodeRightChild;
	}
	else
	{
		if (pNode->pParent->pLeft == pNode)
			pNode->pParent->pLeft = pNodeRightChild;
		else
			pNode->pParent->pRight = pNodeRightChild;
		pNodeRightChild->pParent = pNode->pParent;
	}
	//pNode作为pNodeRightChild的左孩子
	pNodeRightChild->pLeft = pNode;
	//pNode的父节点为pNodeRightChild
	pNode->pParent = pNodeRightChild;

	//pNodeRightChild作为pNode
	pNode = pNodeRightChild;
}

/*
旋转步骤:
1、pNode左孩子的右子树插入pNode的左子树上,如果pNode左子树的右子树不为null,则该右子树的父亲为pNode
2、pNode的父节点设为pNodeLeftChild的父节点
3、判断pNode的父节点是否为null,为null则说明pNode为根节点,应将其右节点作为pRoot
不为null,则应判断pNode是左子树还是右子树,将pNodeLeftChild作为相应的子树
4、pNode作为其左孩子的右子树,pNode的父节点指向pNodeLeftChild
5、将pNodeLeftChild设为当前节点pNode
*/
template<typename T>
void RBTree<T>::RightRotate(RBTreeNode<T>*& pNode)
{
	//pNode左孩子的右子树插入pNode的左子树上
	RBTreeNode<T> *pNodeLeftChild = pNode->pLeft;
	pNode->pLeft = pNodeLeftChild->pRight;
	//如果pNode左子树的右子树不为null,则该右子树的父亲为pNode
	if (pNodeLeftChild->pRight != nullptr)
	{
		pNodeLeftChild->pRight->pParent = pNode;
	}

	//pNodeLeftChild的父亲设为pNode的父亲
	pNodeLeftChild->pParent = pNode->pParent;

	//如果pNode为根节点,则pNodeLeftChild设为根节点
	if (pNode->pParent == nullptr)
		pRoot = pNodeLeftChild;
	else//根据pNode是其父节点的左or右孩子,将pNodeLeftChild设为相应的孩子
	{
		if (pNode->pParent->pLeft == pNode)
			pNode->pParent->pLeft = pNodeLeftChild;
		else
			pNode->pParent->pRight = pNodeLeftChild;
		pNodeLeftChild->pParent = pNode->pParent;
	}
	//pNode作为其左孩子的右子树
	pNodeLeftChild->pRight = pNode;
	//pNode的父节点为pNodeLeftChild
	pNode->pParent = pNodeLeftChild;

	//pNodeLeftChild作为pNode
	pNode = pNodeLeftChild;
}

/*
调整平衡分为以下几种情况
1、红黑树为null,则新插入节点作为根节点,变色为黑色
2、插入节点的父节点为黑色,直接插入即可

3、插入节点的父节点为红色

	3.1插入节点的父节点为插入节点的爷爷节点的左孩子
		3.1.1插入节点的叔叔节点存在,且为红色
			 将新插入节点的父节点和其叔叔节点设置为黑色即可,爷爷节点设置为红色,将爷爷节点设置为当前节点
		3.1.2 叔叔节点不存在(其实叔叔节点不可能为黑色,因为在新插入节点之前,父节点是红色,破坏了黑色平衡,故叔叔节点一定是不存在的)
		(1)插入节点为其父节点的左孩子
				将父节点设置为黑色,爷爷节点设置为红色,对其爷爷节点进行右旋
		(2)插入节点为其父节点的右孩子
			先对父节点进行左旋,将插入节点设置为黑色,爷爷节点设置为红色,然后对其爷爷节点进行右旋

	3.2插入节点的父节点为插入节点的爷爷节点的右孩子
		3.2.1 插入节点的叔叔节点存在,且为红色
			将新插入节点的父节点和其叔叔节点设置为黑色即可,爷爷节点设置为红色,将爷爷节点设置为当前节点
		3.2.2 叔叔节点不存在(其实叔叔节点不可能为黑色,因为在新插入节点之前,父节点是红色,破坏了黑色平衡,故叔叔节点一定是不存在的)
		(1)插入节点为其父节点的右孩子
			将父节点设置为黑色,爷爷节点设置为红色,对其爷爷节点进行左旋
		(2)插入节点为其父节点的左孩子
			先对其父节点进行右旋,将插入节点设置为黑色,爷爷节点设置为红色,然后对其爷爷节点进行左旋
*/
template<typename T>
void RBTree<T>::InsertNodeAmend(RBTreeNode<T>*& pNode, RBTreeNode<T> *pNewNode)
{
	RBTreeNode<T> *pParent, *pGParent, *pUncle;

	pParent = pNewNode->pParent;
	
	//只有当第三种情况发生时才会发生红黑树的调整 
	while (pParent != nullptr && pParent->color == RED)
	{
		pGParent = pParent->pParent;
		//插入节点的父节点为插入节点的爷爷节点的左孩子
		if (pParent == pGParent->pLeft)
		{
			pUncle = pGParent->pRight;
			//叔叔节点存在且为红色
			//将新插入节点的父节点和其叔叔节点设置为黑色即可,将爷爷节点设置为当前节点
			if (pUncle && pUncle->color == RED)
			{
				SetBlack(pUncle);
				SetBlack(pParent);
				SetRed(pGParent);
				pNewNode = pGParent;
			}
			//叔叔节点不存在
			if (pUncle == nullptr)
			{
				//插入节点为其父节点的左孩子
				//将父节点设置为黑色,对其爷爷节点进行右旋
				if (pParent->pLeft == pNewNode)
				{
					SetRed(pGParent);
					SetBlack(pNewNode);
					RightRotate(pGParent);
				}
				//插入节点为其父节点的右孩子
				//先对父节点进行左旋,将插入节点设置为黑色,爷爷节点设置为红色,然后对其爷爷节点进行右旋
				else
				{
					LeftRotate(pParent);
					SetBlack(pParent);//此时插入节点指向pParent
					SetRed(pGParent);
					RightRotate(pGParent);
				}
			}
		}
		//插入节点的父节点为插入节点的爷爷节点的右孩子
		else
		{
			pUncle = pGParent->pLeft;//叔叔节点为父节点的左孩子
			//叔叔节点存在且为红色
			//将新插入节点的父节点和其叔叔节点设置为黑色即可,将爷爷节点设置为当前节点
			if (pUncle && pUncle->color == RED)
			{
				SetBlack(pUncle);
				SetBlack(pParent);
				SetRed(pGParent);
				pNewNode = pGParent;
			}
			//叔叔节点不存在
			if (pUncle == nullptr)
			{
				//插入节点为其父节点的右孩子
				//将父节点设置为黑色,爷爷节点设置为红色,对其爷爷节点进行左旋
				if (pNewNode == pParent->pRight)
				{
					SetBlack(pParent);
					SetRed(pGParent);
					LeftRotate(pGParent);
				}
				//插入节点为其父节点的左孩子
				//先对其父节点进行右旋,将插入节点设置为黑色,爷爷节点设置为红色,然后对其爷爷节点进行左旋
				else
				{
					RightRotate(pParent);
					SetBlack(pParent);//此时插入节点指向pParent
					SetRed(pGParent);
					LeftRotate(pGParent);
				}
			}

		}
	}

	//无根节点,新插入节点做根节点,设置为黑色
	SetBlack(pRoot);
}

template<typename T>
void RBTree<T>::Print(RBTreeNode<T>* pNode, T data, int direction)
{
	if (pNode != NULL)
	{
		if (direction == 0)    // tree是根节点
			std::cout << std::setw(2) << pNode->data << "(B) is root" << std::endl;
		else                // tree是分支节点
			std::cout << std::setw(2) << pNode->data << ((pNode->color == RED) ? "(R)" : "(B)") << " is " << std::setw(2) << data << "'s " << std::setw(12) << (direction == 1 ? "right child" : "left child") << std::endl;

		Print(pNode->pLeft, pNode->data, -1);
		Print(pNode->pRight, pNode->data, 1);
	}
}

template<typename T>
void RBTree<T>::PreTravel(RBTreeNode<T>* pNode)
{
	if (pNode != nullptr)
	{
		std::cout << pNode->data << std::endl;
		PreTravel(pNode->pLeft);
		PreTravel(pNode->pRight);
	}
}

//后序遍历来删除
template<typename T>
void RBTree<T>::Destroy(RBTreeNode<T>* pNode)
{
	while (pNode)
	{
		Destroy(pNode->pLeft);
		Destroy(pNode->pRight);
		//节点
		delete pNode;
		pNode = nullptr;
	}
}
// RBTree.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include "RBTree.h"

int main()
{
	int a[] = { 1, 4, 10, 6, 90, 7, 2, 110, 81 };
	RBTree<int> tree;
	int len = sizeof(a) / sizeof(a[0]);

	for (int i = 0; i < len; i++)
	{
		tree.Insert(a[i]);
		std::cout << "== 添加节点: " << a[i] << std::endl;
		std::cout << "== 树的详细信息: " << std::endl;
		tree.Print();
		std::cout << std::endl;
	}
		

	//tree.PreTravel();


	return 0;

}

 

  • 13
    点赞
  • 82
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值