红黑树(RBTree)的插入算法以及如何测试一棵树是否是红黑树?(详细图解说明)

本文主要是介绍红黑树(RBTree)的插入算法以及如何测试一棵树是否是红黑树?(详细图解说明),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.什么叫红黑树?

             红黑树是一棵二叉搜索树,它在每个节点上增加了一个存储位来表示节点的颜色,可以是Red或Black。通过对任何一条从根到叶子简单路径上的颜色来约束,红黑树保证最长路径不超过最短路径的两倍,因而近似于平衡。


2.红黑树的性质

     

   1. 每个节点,不是红色就是黑色的

   2. 根节点是黑色的

   3. 如果一个节点是红色的,则它的两个子节点是黑色的(没有连续的红色结点

   4. 对每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。(每条路径的黑色结点个数相等)

   5. 每个叶子节点都是黑色的(这里的叶子节点是指的NIL节点(空节点))


3.红黑树为什么通过颜色的约束能够保证最长路径不超过最短路径的两倍?




4.红黑树的结点的定义

enum Colour
{RED,BLACK
};template<typename K,typename V>
struct RBTreeNode
{//结点RBTreeNode* _left;RBTreeNode* _right;RBTreeNode* _parent;//颜色Colour _col;//KVK _key;V _value;RBTreeNode(const K& key, const V& value):_key(key), _value(value), _left(NULL), _right(NULL), _parent(NULL), _col(RED)  //把新增结点置成红色为了保持每条路径的黑色结点个数不变{}
};


为什么把新增结点的颜色设置为红色而不是黑色呢?

    因为设置为红色,比较容易维护这棵树,设置为黑色,会导致当前这条路径多了一个黑色结点,为了满足每条路径的黑色结点个数相同,需要维护多条路径。



5.红黑树插入的几种情况


注:由于p结点(父结点)可能为g结点(祖父结点)的左孩子或者右孩子,下面的作图以左孩子为模板,右孩子与左孩子类似,只是在某些需要旋转的情况下不一样。

    (1)插入结点为根节点(只有一个结点的情况),将结点颜色置成黑色即可

    

    (2)插入结点的父结点或调整结点的父结点为黑色结点(不需要调整)



  

  (3)插入结点的父结点或向上调整的结点父结点为红色之——uncle结点存在为红色

  

  (4)插入结点的父结点为红色之——uncle结点不存在



  (5)插入结点的父结点为红色之——uncle结点存在为黑色





6.红黑树插入实现代码


template<class K,class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public:RBTree():_root(NULL){}bool Insert(const K& key, const V& value){//1.插入结点为根节点,必须为黑色if (_root == NULL){_root = new Node(key, value);_root->_col = BLACK;return true;}Node* cur = _root;Node* parent = NULL;while (cur != NULL){if (cur->_key > key){parent = cur;cur = cur->_left;}else if (cur->_key < key){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(key, value);if (key > parent->_key)parent->_right = cur;elseparent->_left = cur;cur->_parent = parent;while (parent != NULL){Node* pparent = parent->_parent;Node* uncle = NULL;//2.如果插入节点或者调整结点的父结点为黑色结点,增加一个红色结点,对每条路径的黑色结点个数没影响if (parent->_col == BLACK){break;}//判断叔叔结点是否存在,存在是在左还是右?if (pparent != NULL){if (pparent->_right == parent)uncle = pparent->_left;elseuncle = pparent->_right;}//3.插入结点或调整结点父结点为红色且叔叔结点为红色if (uncle != NULL&&uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;if (pparent == _root)return true;pparent->_col = RED;//继续向上调整//假如调整到根结点,只需将根结点的颜色置为黑色即可if (parent == _root){parent->_col = BLACK;return true;}cur = parent->_parent;parent = cur->_parent;}//4.插入结点或调整结点父结点为红色且叔叔结点不存在或//  插入结点或调整结点父结点为红色且叔叔结点存在为黑色二者逻辑一样。else//剩下的情况 (uncle == NULL || uncle->_col == BLACK){//判断父结点在左还是在有if (pparent->_left == parent){//左--左if (parent->_left == cur){_RotateR(pparent);pparent->_col = RED;parent->_col = BLACK;if (pparent == _root){_root = parent;}}//左--右else{_RotateL(parent);_RotateR(pparent);cur->_col = BLACK;parent->_col = RED;pparent->_col = RED;if (pparent == _root)_root = cur;}}//父结点在右else{//右--右if (parent->_right == cur){_RotateL(pparent);pparent->_col = RED;parent->_col = BLACK;if (pparent == _root)_root = parent;}//右--左else{_RotateR(parent);_RotateL(pparent);parent->_col = RED;pparent->_col = RED;cur->_col = BLACK;if (pparent == _root)_root = parent;}}break;}}return true;}void InOrder(){_InOrder(_root);cout << endl;}
protected:void _InOrder(Node* root){if (root == NULL)return;_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}void _RotateR(Node* parent){Node* SubL = parent->_left;Node* SubLR = SubL->_right;//链上SubLRparent->_left = SubLR;if (SubLR != NULL)SubLR->_parent = parent;Node* pparent = parent->_parent;//链上SubLSubL->_parent = pparent;if (pparent != NULL){if (pparent->_left == parent)pparent->_left = SubL;elsepparent->_right = SubL;}//链上parentparent->_parent = SubL;SubL->_right = parent;}void _RotateL(Node* parent){Node* SubR = parent->_right;Node* SubRL= SubR->_left;//链上SubLRparent->_right= SubRL;if (SubRL != NULL)SubRL->_parent = parent;Node* pparent = parent->_parent;//链上SubLSubR->_parent = pparent;if (pparent != NULL){if (pparent->_left == parent)pparent->_left = SubR;elsepparent->_right = SubR;}//链上parentparent->_parent = SubR;SubR->_left = parent;}Node* _root;
};

7.如何测试一棵树是不是红黑树?

(1)测试它的根节点是不是黑色

(2)测试是否有连续的红结点

(3)测试每条路径上的黑节点的数量是否相等?

以某一条路径(测试用例用的是左子树)上的黑色结点的个数为判断标准,如果有一条不满足相等,证明不是红黑树。

实现代码:

    

bool Isbalance(){//1.空树认为是RBTreeif (_root == NULL)return true;//2.根结点非黑不是RBTreeif (_root->_col == RED)return false;int count = 0;//以左子树上的黑色结点作为一个基准,如果一样就证明是,不一样就证明不是RBTreeNode* cur = _root;while (cur != NULL){if (cur->_col == BLACK)count++;cur = cur->_left;}//num用来标记每条路径上的黑色结点的个数//count用来标记左子树上的黑色结点的个数int num = 0;return _Isbalance(_root, count, num);}bool _Isbalance(Node* root, int count, int num){if (root == NULL)return true;//3.连续红结点不是RBTree,root结点一定有父结点if (root->_col == RED&&root->_parent->_col == RED){cout << root->_key << " 有连续的红结点" << endl;return false;}if (root->_col == BLACK)num++;if (root->_left == NULL&&root->_right == NULL){if (num != count){cout << root->_key << " 黑色结点个数不一样" << endl;return false;}}return _Isbalance(root->_left, count, num) && _Isbalance(root->_right, count, num);}

  




8.测试用例

测试用例:

void TestRBTree()
{int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };RBTree<int, int> t;for (size_t i = 0; i < sizeof(a) / sizeof(a[0]); i++){t.Insert(a[i], i);}t.InOrder();cout << "Isbalance---->" << t.Isbalance()<< endl;
}


       运行结果和测试用例红黑树模型:





9.红黑树和AVL树比较

      (1)红黑树和AVL树都是高效的平衡二叉树,增删查改的时间复杂度都是O(lg(N))。            

         (2)红黑树的不追求完全平衡,保证最长路径不超过最短路径的2倍,相对而言,降低了旋转的要求,所以性能会优于AVL树,所以实际运用中红黑树更多。(效率差不多要求还低)。

注:

AVL旋转因子的调节(详细图解)



这篇关于红黑树(RBTree)的插入算法以及如何测试一棵树是否是红黑树?(详细图解说明)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/579925

相关文章

将Mybatis升级为Mybatis-Plus的详细过程

《将Mybatis升级为Mybatis-Plus的详细过程》本文详细介绍了在若依管理系统(v3.8.8)中将MyBatis升级为MyBatis-Plus的过程,旨在提升开发效率,通过本文,开发者可实现... 目录说明流程增加依赖修改配置文件注释掉MyBATisConfig里面的Bean代码生成使用IDEA生

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

Linux系统中卸载与安装JDK的详细教程

《Linux系统中卸载与安装JDK的详细教程》本文详细介绍了如何在Linux系统中通过Xshell和Xftp工具连接与传输文件,然后进行JDK的安装与卸载,安装步骤包括连接Linux、传输JDK安装包... 目录1、卸载1.1 linux删除自带的JDK1.2 Linux上卸载自己安装的JDK2、安装2.1

Java使用Curator进行ZooKeeper操作的详细教程

《Java使用Curator进行ZooKeeper操作的详细教程》ApacheCurator是一个基于ZooKeeper的Java客户端库,它极大地简化了使用ZooKeeper的开发工作,在分布式系统... 目录1、简述2、核心功能2.1 CuratorFramework2.2 Recipes3、示例实践3

Tomcat版本与Java版本的关系及说明

《Tomcat版本与Java版本的关系及说明》:本文主要介绍Tomcat版本与Java版本的关系及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Tomcat版本与Java版本的关系Tomcat历史版本对应的Java版本Tomcat支持哪些版本的pythonJ

SpringBoot实现MD5加盐算法的示例代码

《SpringBoot实现MD5加盐算法的示例代码》加盐算法是一种用于增强密码安全性的技术,本文主要介绍了SpringBoot实现MD5加盐算法的示例代码,文中通过示例代码介绍的非常详细,对大家的学习... 目录一、什么是加盐算法二、如何实现加盐算法2.1 加盐算法代码实现2.2 注册页面中进行密码加盐2.

MySQL INSERT语句实现当记录不存在时插入的几种方法

《MySQLINSERT语句实现当记录不存在时插入的几种方法》MySQL的INSERT语句是用于向数据库表中插入新记录的关键命令,下面:本文主要介绍MySQLINSERT语句实现当记录不存在时... 目录使用 INSERT IGNORE使用 ON DUPLICATE KEY UPDATE使用 REPLACE

通过Docker Compose部署MySQL的详细教程

《通过DockerCompose部署MySQL的详细教程》DockerCompose作为Docker官方的容器编排工具,为MySQL数据库部署带来了显著优势,下面小编就来为大家详细介绍一... 目录一、docker Compose 部署 mysql 的优势二、环境准备与基础配置2.1 项目目录结构2.2 基

Java时间轮调度算法的代码实现

《Java时间轮调度算法的代码实现》时间轮是一种高效的定时调度算法,主要用于管理延时任务或周期性任务,它通过一个环形数组(时间轮)和指针来实现,将大量定时任务分摊到固定的时间槽中,极大地降低了时间复杂... 目录1、简述2、时间轮的原理3. 时间轮的实现步骤3.1 定义时间槽3.2 定义时间轮3.3 使用时

Nginx指令add_header和proxy_set_header的区别及说明

《Nginx指令add_header和proxy_set_header的区别及说明》:本文主要介绍Nginx指令add_header和proxy_set_header的区别及说明,具有很好的参考价... 目录Nginx指令add_header和proxy_set_header区别如何理解反向代理?proxy