【C++模拟实现】手撕红黑树(含图解)

news2024/10/6 11:45:59

【C++模拟实现】手撕红黑树(含图解)

目录

  • 【C++模拟实现】手撕红黑树(含图解)
      • 红黑树的介绍(百度百科)
        • 简介
        • 特征(十分重要,红黑树的基础)
      • 红黑树的实现代码(insert部分)
      • 验证是否为红黑树
      • 红黑树的图解
        • 红黑树的正常情况:![请添加图片描述](https://img-blog.csdnimg.cn/8a5fabb884354c178c158e0b9ff8129c.jpeg)
        • 红黑树情况一(uncle节点为红色):
        • 红黑树情况二(需要旋转的情况,uncle节点为黑色或者为空)

作者:爱写代码的刚子

时间:2023.9.11

前言:这次的博客除了红黑树的模拟实现外还附加了图示来帮助理解红黑树,以及编写代码对红黑树的验证。

红黑树的介绍(百度百科)

简介

  • 红黑树是一种特定类型的二叉树,它是在计算机科学中用来组织数据比如数字的块的一种结构。

  • 红黑树是一种平衡二叉查找树的变体,它的左右子树高差有可能大于 1,所以红黑树不是严格意义上的平衡二叉树(AVL),但 对之进行平衡的代价较低, 其平均统计性能要强于 AVL 。

  • 由于每一棵红黑树都是一颗二叉排序树,因此,在对红黑树进行查找时,可以采用运用于普通二叉排序树上的查找算法,在查找过程中不需要颜色信息。

特征(十分重要,红黑树的基础)

红黑树是每个结点都带有颜色属性的二叉查找树,颜色或红色或黑色。 在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:

**性质1. 结点是红色或黑色。 **

**性质2. 根结点是黑色。 **

性质3. 所有叶子都是黑色。(叶子是NIL结点)

性质4. 每个红色结点的两个子结点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色结点)

性质5. 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点。

这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。

是性质4导致路径上不能有两个连续的红色结点确保了这个结果。最短的可能路径都是黑色结点,最长的可能路径有交替的红色和黑色结点。因为根据性质5所有最长的路径都有相同数目的黑色结点,这就表明了没有路径能多于任何其他路径的两倍长。

因为红黑树是一种特化的二叉查找树,所以红黑树上的只读操作与普通二叉查找树相同。

红黑树的实现代码(insert部分)

#include <iostream>
using namespace std;
enum Colour
{
    BLACK,
    RED
};

template<class K,class V>
struct RBTreeNode
{
    RBTreeNode(const pair<K,V>& kv)
    :_left(nullptr)
    ,_parent(nullptr)
    ,_right(nullptr)
    ,_c(RED)//初始为红节点
    ,_kv(kv)
    {}


    RBTreeNode* _left;
    RBTreeNode* _parent;
    RBTreeNode* _right;

    Colour _c; 
    pair<K,V> _kv;

};

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

    bool insert(const pair<K,V>& kv)
    {
         if(_root==nullptr)
        {
            _root=new Node(kv);
            _root->_c=BLACK;
            return true;
        }
        else
        {
            Node* cur=_root;
            Node* parent=nullptr;
            while(cur)
            {
                if(cur->_kv.first>kv.first)
                {
                    parent=cur;
                    cur=cur->_left;
                }
                else if(cur->_kv.first<kv.first)
                {
                    parent=cur;
                    cur=cur->_right;
                }
                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->_c==RED)
        {
            Node* grandfather=parent->_parent;

            if(grandfather->_left==parent)
            {
                Node* uncle=grandfather->_right;
                //判断uncle的几种情况


                if(uncle&&uncle->_c==RED)
                {
                    uncle->_c=parent->_c=BLACK;
                    grandfather->_c=RED;

                    cur=grandfather;
                    parent=cur->_parent;

                
                }
                else 
                {
                    if(cur==parent->_left)
                    {
                        _RotateR(grandfather);

                        grandfather->_c=RED;
                        parent->_c=BLACK;


                    }
                    else{
                        _RotateL(parent);
                        _RotateR(grandfather);

                        grandfather->_c=RED;
                        cur->_c=BLACK;


                    }
                    break;
                }
            }
            else
            {
                Node* uncle=grandfather->_left;
            
                if(uncle&&uncle->_c==RED)
                {
                    uncle->_c=parent->_c=BLACK;
                    grandfather->_c=RED;

                    cur=grandfather;
                    parent=cur->_parent;
                    
                
                }
                else 
                {
                    if(cur==parent->_right)
                    {
                        _RotateL(grandfather);
                        parent->_c=BLACK;
                        grandfather->_c=RED;
                    }
                    else{
                        _RotateR(parent);
                        _RotateL(grandfather);


                        cur->_c=BLACK;
                        grandfather->_c=RED;
                    }
                    break;

                }
                      
            }

        }
        
        }
        _root->_c=BLACK;//头节点始终为黑色
        return true;
    }


    


    void _RotateR(Node* parent)
    {
        Node*cur=parent->_left;
        Node*curRight=cur->_right;
        Node*ppnode=parent->_parent;
        
        
        cur->_right=parent;
        parent->_left=curRight;

        if(curRight)
        {
            curRight->_parent=parent;
        }
        parent->_parent=cur;
        
        //处理ppnode
        if(parent==_root)
        {
            _root=cur;
            cur->_parent=nullptr;

        }
        else
        {
            if(ppnode->_left==parent)
            {
                ppnode->_left=cur;
            }
            else{
                ppnode->_right=cur;
            }
            cur->_parent=ppnode;
        }
     

    }

    void _RotateL(Node* parent)
    {
        Node* cur=parent->_right;
        Node* curLeft=cur->_left;
        Node* ppnode=parent->_parent;

        cur->_left=parent;
        parent->_right=curLeft;

        if(curLeft)
        {
            curLeft->_parent=cur;
        }
        parent->_parent=cur;

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





private:

    Node* _root;
};


验证是否为红黑树

bool CheckColour(Node* root, int blacknum, int benchmark)//并不是最优递归
{
		if (root == nullptr)
		{
			if (blacknum != benchmark)
				return false;

			return true;
		}

		if (root->_c == BLACK)
		{
			++blacknum;
		}

		if (root->_c == RED && root->_parent && root->_parent->_c == RED)
		{
			cout << root->_kv.first << "有连续红色节点" << endl;
			return false;
		}

		return CheckColour(root->_left, blacknum, benchmark)
			&& CheckColour(root->_right, blacknum, benchmark);
}
bool IsBalance()
	{
		return IsBalance(_root);
	}

	bool IsBalance(Node* root)
	{
		if (root == nullptr)
			return true;

		if (root->_c != BLACK)
		{
			return false;
		}

		// 基准值
		int benchmark = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_c == BLACK)
				++benchmark;

			cur = cur->_left;
		}

		return CheckColour(root, 0, benchmark);
	}
  • 添加以上成员函数可以来验证该红黑树的黑节点数量是否正确,以及是否出现连续红色节点。
  • 以下为测试代码:请添加图片描述

红黑树的图解

红黑树的正常情况:请添加图片描述

红黑树情况一(uncle节点为红色):

请添加图片描述

  • 由于不能出现连续的红色节点,所以我们需要进行处理,此时我们有以下结论:

    当parent为RED节点时说明我们插入了一个节点(红黑树默认插入RED节点)导致出现了连续的红色节点

    当uncle节点存在且为红,我们需要将parent和uncle节点变为黑色,将grandfather变为红色

    最后将grandfather赋值给cur,parent指向cur的_parent,进入下一个循环,直到parent为黑色节点

红黑树情况二(需要旋转的情况,uncle节点为黑色或者为空)

  1. parent为grandfather的左节点,且cur为parent的左节点(右单旋

    请添加图片描述

  2. parent为grandfather的右节点,且cur为parent的右节点(左单旋

原理和右单旋几乎相同,图略。

  1. parent为grandfather的右节点,且cur为parent的左节点(右左双旋

    请添加图片描述

  2. parent为grandfather的左节点,且cur为parent的右节点(左右双旋

原理和右左双旋几乎相同,图略。


附:

  • 降序插入构建红黑树动图:

请添加图片描述

  • 左右旋转的成员函数复用了avl树的旋转函数。
  • 左右旋转图解:请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1004405.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

运营商大数据合作方合作流程和具体服务流程是什么?

运营商大数据合作方合作的流程分为好几个阶段。首先是要进行合作咨询&#xff0c;咨询完成了以后&#xff0c;再直接对相关的数据进行相应的评估。接着再把资源整合起来&#xff0c;然后再对数据进行清洗&#xff0c;接着直接将产品进行包装&#xff0c;然后给数据定价&#xf…

Linux时区配置

Linux时区配置 timedatectl设置时区和时间启用自动同步NTP时间设置UTC或RTC时间查看UTC时间 查看所有可用时区查看当前时区设置系统时区启用夏令时timedatectl修改当前日期时间 通用设置时区方法使用tzselect设置时区 timedatectl设置时区和时间 timedatectl是一个系统工具&am…

解决微信小程序报错:“SyntaxError:Unexpected end of JSON input”

1. 报错原因&#xff1a; 如果对象的参数或数组的元素中遇到地址&#xff0c;地址中包括?、&这些特殊符号时&#xff0c;对象/数组先要通过JSON.stringify转化为字符串再通过encodeURIComponent编码&#xff1b;接收时&#xff0c;先通过decodeURIComponent解码再通过JSON…

成集云 | 聚水潭对接金蝶云星空接口 | 解决方案

源系统成集云目标系统 方案介绍 聚水潭是一款以SaaS ERP为核心&#xff0c;集多种商家服务为一体的SaaS协同平台&#xff0c;为全国35万多家电商企业提供全面的信息化解决方案。聚水潭已在全国设立了超过60个线下服务网点&#xff0c;服务范围覆盖超过300个城市&#xff0c;为…

05目标检测-区域推荐(Anchor机制详解)

目录 一、问题的引入 二、解决方案-设定的anchor boxes 1.高宽比&#xff08;aspect ratio&#xff09;的确定 2.尺度(scale)的确定 3.anchor boxes数量的确定 三、Anchor 的在目标检测中是怎么用的 1、anchor boxes对真值bounding box编码的步骤 2、为什么要回归偏移量…

Linux安装jdk11

官网下载链接 https://www.oracle.com/java/technologies/downloads/#java11 下载解压并更改环境变量 export JAVA_HOME/home/jenkins/jdk-11.0.20 PATH"$HOME/.local/bin:$HOME/bin:$JAVA_HOME/bin:$PATH"

5G网络优化工程师如何挑选最合适的电脑?

最近很多要来学习5G网络优化的小伙伴都在问小编什么样的电脑会比较合适&#xff0c;开学季嘛电子产品都比较的优惠&#xff0c;想要下手一台性价比较高的笔记本 &#xff0c;希望小编能够给点建议&#xff0c;于是小编马不停蹄的联系了老师以及就业的学员详细的了解之后&#x…

Error: That port is already in use.

刚开始看后端的内容 经常遇到的问题就是Error: That port is already in use. 解决方法 有两个方法可以解决(假设8000端口被占用)&#xff1a; 1. 使用python manage.py runserver 8001 开一个新的端口。 2. kill掉原来的端口(在root条件下)。 在终端输入lsof -i:8000&am…

3dmax 基础操作

基础快捷键 1.放大视图 Altw 2.缩放 滑动滚轮 3.移动物体 按w 移动画布 ctrlp/鼠标中键 4.旋转视图 alt鼠标中键 5.复制 shift移动 6.视图切换 P透视 T顶视图 F前视图 L左视图 7.线框模型显示 f4 8. 只显示线框不显示模型 f3…

多场景PAI-Diffusion中文模型家族大升级,12个模型、2个工具全部开源

作者&#xff1a; 段忠杰、刘冰雁、汪诚愚、邹心怡、黄俊 概述 在过去几年中&#xff0c;随着AI生成内容&#xff08;AI Generated Content&#xff0c;AIGC&#xff09;的快速发展&#xff0c;Stable Diffusion模型在该领域崭露头角。阿里云机器学习PAI团队为推动这一领域的…

三、日志编写 —— TinyWebServer

日志编写 —— TinyWebServer 一、 前言 上期已经写完lock类的编写。这期是日志的编写。 对于日志需要弄懂几个基本概念才可以更好的理解和编写日志。 什么是日志&#xff1f;常用的日志级别有哪些&#xff1f;日志的基本格式是什么&#xff1f;异步日志类刷新缓冲区的作用同…

爱尔眼科周进院长出席CCOS 2023并担任学术讲者及培训导师

星城长沙&#xff0c;大咖云集&#xff0c;护航光明&#xff0c;共创未来!9月6日—10日&#xff0c;国内眼科学界最盛大的学术会议——中华医学会第二十七次全国眼科学术大会(CCOS 2023)在湖南长沙隆重举办!逾万名国内外眼科专家、学者代表参加盛会&#xff0c;聚焦眼科发展的新…

骨传导耳机弊端都有哪些?盘点骨传导耳机有哪些缺点!

骨传导耳机有弊端吗&#xff1f;答案是有的&#xff01;不过由于骨传导耳机的优点大于缺点&#xff0c;所以骨传导耳机的弊端可以忽略不计&#xff0c;下面跟大家说下骨传导耳机都有什么弊端。 1、漏音情况 不管什么耳机&#xff0c;都会存在漏音情况&#xff0c;只是漏音的…

恒运资本:早盘三大指数震荡 减肥药概念再度大涨!

周二&#xff08;9月12日)&#xff0c;A股三大股指震动&#xff0c;涨跌互现。到上午收盘&#xff0c;上证指数涨0.04%&#xff0c;报3144.03点&#xff1b;深证成指涨0.32%&#xff0c;报10415.22&#xff1b;创业板指跌0.10%&#xff1b;沪深两市算计成交额4492.47亿元&#…

C++ 特性模版

目录 1. 非类型模板参数 2. 模板的特化 2.1 概念 2.2 函数模板特化 2.3 类模板特化 2.3.1 全特化 2.3.2 偏特化 2.3.3 类模板特化应用示例 3 模板分离编译 3.1 什么是分离编译 3.2 模板的分离编译 3.3 解决方法 4. 模板总结 1. 非类型模板参数 模板参数分类类型形…

【SpringCloud微服务项目实战-mall4cloud项目(2)】——mall4cloud-gateway

mall4cloud-gateway网关模块 系统架构与网关介绍网关介绍 网关层代码依赖引入bootstrap配置nacos配置gateway配置处理跨域问题gateway路由断言配置gateway路由过滤配置 总结 代码地址 github地址 fork自github原始项目 gitee地址 fork自gitee原始项目 系统架构与网关介绍 从图…

企业为什么要做网络安全?

网络安全任何人都无法独善其身&#xff0c;无论个个人还是企业或者是高校、机构等&#xff0c;都需要参与到网络安全维护中。 在当今数字化时代&#xff0c;随着互联网的普及和企业信息化程度的提高&#xff0c;网络安全成为了企业运营中不可忽视的重要问题。网络安全指的是通过…

浅谈限流式保护器在高校防火工作的应用

安科瑞 华楠 【摘要】摘要&#xff1a;为了预防火灾和减少火灾带来的危害&#xff0c;保护校园和师生生命财产安全&#xff0c; 建和谐安宁的校园环境&#xff0c;保障学校安全稳定发展&#xff0c;我们必须要时刻拧紧消防安全这弦&#xff0c;时刻注意这根高压线。随着近年来…

RFID标签:实现固定资产全生命周期管理与极速盘点的利器

随着企业规模的扩大和业务的发展&#xff0c;固定资产管理变得越来越重要。然而&#xff0c;传统的手工管理方式往往效率低下、容易出错&#xff0c;无法满足现代企业对高效、准确管理的需求。在这样的背景下&#xff0c;RFID&#xff08;Radio Frequency Identification&#…

C++项目实战——基于多设计模式下的同步异步日志系统-⑦-日志输出格式化类设计

文章目录 专栏导读日志格式化类成员介绍patrernitems 格式化子项类的设计抽象格式化子项基类日志主体消息子项日志等级子项时间子项localtime_r介绍strftime介绍 源码文件名子项源码文件行号子项线程ID子项日志器名称子项制表符子项换行符子项原始字符串子项 日志格式化类的设计…