首页 > 其他分享 >【数据结构】红黑树

【数据结构】红黑树

时间:2024-12-19 15:28:51浏览次数:6  
标签:cur parent color 红黑树 grandparent 数据结构 节点 left

目录

一、概念

二、红黑树的插入

(一)插入步骤

(二)插入的三种情况

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,因此我们默认插入节点颜色为红色。

(一)插入步骤

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

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

(二)插入的三种情况

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

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;
};

标签:cur,parent,color,红黑树,grandparent,数据结构,节点,left
From: https://blog.csdn.net/Sweet_0115/article/details/144577423

相关文章

  • Java笔记(数据结构与算法[树、栈、列表、队列、数组])
    Java笔记(数据结构与算法[树、栈、列表、队列、数组])链表栈,队列,数组树易错点:二叉树的插入,数据往二叉树里面插入的时候,每一个数据都要和每一个节点相比较,不可能插入到某两个节点中间,最后一定是挂(添加)到二叉树的最后一排的某个节点上度:每......
  • 数据结构:贪吃蛇详解
    目录一.地图的设计1.字符与坐标:2.本地化(头文件):3.类项:4.setlocale函数:(1)函数原型:(2)使用:5.宽字符的打印:(1)宽字符是什么:(2)打印:6.地图的实现和打印:(1)具体代码与预期实现效果:(2)解释:二.游戏逻辑与大体框架1.游戏逻辑与大体框架:2.游戏初步实现(GameStart函数):(1)欢迎界面......
  • 数据结构与算法学习笔记----Dijkstra
    数据结构与算法学习笔记----Dijkstra@@author:明月清了个风@@firstpublishtime:2024.12.17ps⭐️两个版本的都是模版题,算法讲解直接放在每一题里了,思路中还有对于稀疏图和稠密图的介绍,注意优化版的dijkstra中有几点注意点是和朴素版不一样的。Acwing849.Dijkstr......
  • 数据结构与算法学习笔记----SPFA算法
    数据结构与算法学习笔记----SPFA算法@@author:明月清了个风@@firstpublishtime:2024.12.19SPFA算法这同样是一种单源最短路径算法,是队列优化的Bellman-ford算法,因此他能处理的情况和Bellman-ford算法一样,但是在一般情况下,时间复杂度更低,为......
  • Java四种同步数据结构对比
    前言相信各位在遇到并发场景处理数据时都碰到过该选什么数据结构进行存储的问题,本文就Java中常用的四种数据结构进行简单的讨论正文ConcurrentLinkedQueueConcurrentLinkedQueue是java.util.concurrent(JUC)包下的一个线程安全的队列实现。基于非阻塞算法(Michael-Scott非阻塞......
  • 数据结构实验题目剖析·下篇(PTA平台原题)
    目录补强:A3.PAT考试排名汇总(☆☆)要点剖析:逐步分析:代码分析: 实验结果: A4.旅游规划问题(☆☆)要点剖析: 逐步分析:代码分析:实验结果:数据结构实验题目剖析·上篇(PTA平台原题)补强:这里对上一期的第二题进行一个单独的加强,这里有一个新的思路和代码来和大家......
  • 数据结构维护技巧(长期更新)
    拜谢lxl维护函数复合大概是每个位置上有一个函数\(f(x)\),给出\([L,R]\)和初值\(v\),算\(f_R(f_{R-1}(\dotsf_L(v)\dots))\)。有个东西叫插入-标记-回收算法。首先将所有询问离线,然后拿扫描线扫一遍序列。维护一个集合\(S\),存每个询问的结果。插入:扫到\(i\)后,如果这个地方是......
  • Day6数据结构
    完成单向循环链表的所有操作【创建、判空、尾插、遍历、尾删、销毁】looplink.h#ifndef__LOOPLINK_H__#define__LOOPLINK_H__#include<stdio.h>#include<stdlib.h>#include"looplink.h"typedefintDateType;typedefstructnode{ union { DateTypedate; ......
  • 从上千份大厂面经呕心沥血整理:大厂高频手撕面试题(数据结构篇 ,Java实现亲试可跑)
    怎么判断两个链表是否相交?怎么优化?判断两个链表是否相交可以采用多种方法。一种方法是使用双指针。首先分别遍历两个链表,得到两个链表的长度。然后让长链表的指针先走两个链表长度差的步数。之后,同时移动两个链表的指针,每次比较两个指针是否指向相同的节点。如果指向相同......
  • 数据结构与算法分析-Chapter1
    Chapter1-绪论1.1数据结构的基本概念1.数据(data)        主要包括数值型数据和非数值型数据。2.数据元素(dataelement)        描述数据的基本单位。可以由多个数据项(dataitem)组成。        数据项是具有独立含义的最小标识单位。例如描述......