「C++」红黑树的插入(手撕红黑树系列)

在这里插入图片描述

💻文章目录

  • 📄前言
  • 红黑树
    • 概念
    • 红黑树的结构
      • 红黑树节点的定义
      • 红黑树的定义
      • 红黑树的调整
    • 红黑树的迭代器
      • 迭代器的声明
      • operator( )++
      • opeartor--( )
    • 完整代码
  • 📓总结


📄前言

作为一名程序员相信你一定有所听闻红黑树的大名,像是手撕红黑树这样的名梗已经几乎传遍了程序员之间,如果你还不会“手撕”红黑树,那么本文将会教会你如何“手撕”红黑树。

红黑树

概念

红黑树,顾名思义是只有红色和黑色两种颜色的树,由 Rudolf Bayer 在1972年发明的。红黑树是一种高效的查找树,可以在 O ( l o g 2 n ) O(log_2n) O(log2n)的时间复杂度下进行查找、插入和删除,C++中的map和set的底层也是利用红黑树所构成,在深入学习红黑树前,先让我们学习一下它的特性吧。

红黑树的特性:

  1. 根节点为黑
    t2. 最长路径的长度不超过最短路径的长度的两倍
  2. 每条路径的黑色节点之和都相同
  3. 不能存在连续的红色节点
  4. 只存在红色或黑色的节点
  5. 中序遍历是有序的

红黑树的样例:
在这里插入图片描述在这里插入图片描述

从图例我们可以看出每条路径的黑色节点个数都是相同的并且没有连续的红色节点,只要满足这两条特性,红黑树的最长路径节点个数不会超过最短节点个数的两倍,从而维护了树的平衡。

红黑树的结构

红黑树节点的定义

在进入插入操作前,得先定义好树的节点。因为树的插入需要用到父节点、甚至祖父节点,所以为了方便插入,二叉树的节点新增了父节点的指针。

enum Color	//颜色的定义
{RED,	//0BLACK	//1
};template <class _Value>
struct RBTreeNode		//红黑树节点的定义
{RBTreeNode<_Value>* _left;	//节点的左孩子RBTreeNode<_Value>* _right;	//节点的右孩子RBTreeNode<_Value>* _parent;	//节点的双亲Color _col;		//节点的颜色_Value _data;			//节点的数值RBTreeNode(const _Value& data = _Value())	//节点的构造函数:_left(nullptr),_right(nullptr),_parent(nullptr),_data(data),_col(RED)	//默认设节点为红色{}
};

红黑树的定义

C++的红黑树在实现上为了同时让map和set复用,增加了一个keyofvalue的模板参数,用来解析需要比较的数值,如果不打算实现set和map可以不用写。

template<class _Key, class _Value, class _KeyOfValue>	
/*如果愿意还可以加上一个compare参数,来比较数值*/
class RBTree
{
public:typedef RBTreeNode<_Value> Node;/*这里暂时先把insert的返回值设为Node*,迭代器后面介绍时再补充*/Node* insert(const _Value data)		{if(_root == nullptr)			//节点为空则新建{_root = new Node(data);_root->_col = BLACK;		//红黑书性质规定根节点必须为黑return _root;}_KeyOfValue kot;		//用来解析数据的伪函数Node* cur = _root;Node* parent = nullptr;		while(cur)			/*二叉树搜索树的经典搜索过程*/{//工作原理:是data是pair类型则返回data.first,正常内置类型直接返回dataif(kot(cur->_data) < kot(data))		{															parent = cur;cur = cur->_right;}else if(kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}else return cur;}cur = new Node(data);Node* ret = cur;cur->_parent = parent;		/*链接父节点*//*父节点链接子节点*/if(kot(cur->_data) < kot(parent->_data))parent->_left = cur;else parent->_right = cur;	/***************检查红黑树是否违反性质**************/}
}

红黑树的调整

红黑树的每次插入都需要检查其性质是否遭到了破坏,因为节点默认颜色为红色,所以当父节点为黑色时,则不需要调整。如果父节点为红色,违反了红黑树的性质,根据红黑树的情况,共有六种情况需要讨论,其中需要利用到祖父节点,根据父节点在祖父节点的左孩子/右孩子,又将6种情况划分为两类。

为了方便讨论,这里把当前节点作为cur,cur的父节点为p,cur的祖父节点为g,p的兄弟节点为u

  • 父节点是祖父节点的左孩子

    • 情况一:cur为红,p为红,g为黑,u存在且为红

      这种情况下,需要把p节点和u节点设为黑色,如果g节点为根节点则退出调整,否则将g节点设为红色,并把g赋值给cur,继续向上调整。
      在这里插入图片描述

      if(uncle && uncle->_col == RED)
      {parent->_col = uncle->_col = BLACK;grandParent->_col = RED;cur = grandParent;parent = cur->_parent;
      }
      
    • 情况二:cur为红,p为红,g为黑,u不存在/存在且为黑,并且cur为p的左孩子

      这种情况下,需要对p节点进行右旋操作,并将p节点改为黑,cur和g节点改为红
      在这里插入图片描述

      if(uncle && uncle->_col == BLACK)
      {parent->_col = uncle->_col = BLACK;grandParent->_col = RED;cur = grandParent;parent = cur->_parent;
      }
      
    • 情况三:cur为红,p为红,g为黑,u不存在/存在且为黑,并且cur为p的左孩子
      在这种情况下,需要对双旋操作,先对p节点进行左旋,使得树变得极端左倾,然后再对g节点进行右倾恢复平衡,最后将g改为红,p改为黑。

      在这里插入图片描述

      else {RotateL(parent);RotateR(grandParent);grandParent->_col = RED;cur->_col = BLACK;
      }
      
  • 父节点是祖父节点的右孩子

    • 情况四:cur为红,p为红,g为黑,u存在且为红
      与情况一的处理一样
      在这里插入图片描述

      if(uncle && uncle->_col == BLACK)
      {parent->_col = uncle->_col = BLACK;grandParent->_col = RED;cur = grandParent;parent = cur->_parent;
      }
      
    • 情况五:cur为红,p为红,g为黑,u不存在/存在且为黑, 并且cur为p的左孩子
      这种情况下,需要对g节点进行左旋操作,并把p节点改黑、g节点改红。
      在这里插入图片描述

      if(cur == parent->_right)
      {RotateL(grandParent);parent->_col = BLACK;grandParent->_col = RED;
      }
      
    • 情况六:cur为红,p为红,g为黑,u不存在/存在且为黑, 并且cur为p的右孩子
      这种情况下,需要对p节点进行右旋,使树变得极端右倾,然后对g节点进行左旋,最后将g节点改红、cur节点改黑。
      在这里插入图片描述

    else 
    {RotateR(parent);RotateL(grandParent);grandParent->_col = RED;cur->_col = BLACK;
    }
    

红黑树的迭代器

做完了树的插入,接下来就是红黑树的迭代器了。因为红黑树是平衡树,所以它的最小节点在树的最左侧,最大节点在树的最右侧,为此我们可以使用一个头节点,让其左右孩子指向最大最小节点,父节点指向跟节点。

在这里插入图片描述

迭代器的声明

template <class T, class Ref, class Ptr>	//Ref、Ptr用于const_iterator
struct _TreeIterator
{typedef RBTreeNode<T> Node;typedef _TreeIterator<T, Ref, Ptr> self;Node* _node;self& operator--();self& operator++();
}

operator( )++

找平衡树的下一个比当前节点大的节点,有两种情况

  • 当前节点存在右节点,则找右节点最左边的节点。
  • 不存在右节点,则返回父节点直到当前节点不是父节点的左节点
self& operator++() /*寻找下一个更大节点*/
{if(_node->_right)	{Node* cur = _node->_right;while(cur->_left)		/*寻找最左侧节点*/cur = cur->_left;_node = cur;}else {Node* cur = _node;		Node* parent = cur->_parent;		while(parent && cur == parent->_right){		/*右子树不存在,继续向上调整*/cur = parent;parent = parent->_parent;}_node = parent;}return *this;
}

opeartor–( )

寻找上一节点也分两种情况。

  • 当前节点左孩子存在,则找到左孩子的最右侧节点。
  • 当前节点不存在左孩子,则向上寻找直到当前节点不再是父节点的左孩子
self& operator--()
{Node* cur = _node;if(cur->_col == RED && cur->_parent->_parent == cur){				//当前节点为头节点cur = cur->_right;}if(cur->_left){		//左子树存在,在左子树寻找最大节点cur = cur->_left;while(cur->_right)cur = cur->_right;}else{		//向上调整Node* parent = cur->_parent;while(parent && cur == parent->_left){cur = parent;parent = parent->_parent;}cur = parent;}_node = cur;return *this;
}

完整代码

template <class T, class Ref, class Ptr>
struct _TreeIterator		//迭代器
{typedef RBTreeNode<T> Node;typedef _TreeIterator<T, Ref, Ptr> self;typedef _TreeIterator<T, T&, T*> iterator;  Node* _node;_TreeIterator(Node* node):_node(node){}_TreeIterator(const iterator& _it) //构造函数,方便以后实现set中的inset函数中的pair拷贝:_node(_it._node){}Ref operator*()const{return _node->_data;}Ptr operator->()const{return &operator*();}self& operator--(){Node* cur = _node;if(cur->_col == RED && cur->_parent->_parent == cur){			cur = cur->_right;}if(cur->_left){cur = cur->_left;while(cur->_right)cur = cur->_right;}else{Node* parent = cur->_parent;while(parent && cur == parent->_left){cur = parent;parent = parent->_parent;}cur = parent;}_node = cur;return *this;}self&& operator--(int){self tem = *this;Node* cur = _node;if(cur->_col == RED && cur->_parent->_parent == cur){cur = cur->_right;}if(cur->_left){cur = cur->_left;while(cur->_right)cur = cur->_right;}else{Node* parent = cur->_parent;while(parent && cur == parent->_left){cur = parent;parent = parent->_parent;}cur = parent;}_node = cur;return tem;}self& operator++(){if(_node->_right){Node* cur = _node->_right;while(cur->_left)cur = cur->_left;_node = cur;}else {Node* cur = _node;Node* parent = cur->_parent;while(parent && cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}bool operator!=(const self& s){return _node != s._node;}bool operator==(const self& s){return _node == s._node;}
};template<class K, class T, class KeyOfT>	//可是选择加上 class compare
class RBTree
{
public:typedef RBTreeNode<T> Node;typedef _TreeIterator<T, T&, T*> iterator;typedef _TreeIterator<T, const T&, const T*> const_iterator;		RBTree(){		//提前开好头节点_root = new Node;_root->_left = _root;_root->_right = _root;}const_iterator begin() const {return const_iterator(LeftMost());}const_iterator end() const {return const_iterator(_root);}iterator begin(){return iterator(LeftMost());}iterator end(){return iterator(_root);}std::pair<iterator, bool> Insert(const T& data);		//上文insert返回值设为了Node*,但实际应该是这个// 检测红黑树中是否存在值为data的节点,存在返回该节点的地址,否则返回nullptriterator Find(const K& data);const_iterator Find(const K& data) const;// 获取红黑树最左侧节点Node* LeftMost()const;// 中序遍历void InOrder() {_InOrder(GetRoot());std::cout << std::endl;}// 获取红黑树最右侧节点Node* RightMost()const;// 检测红黑树是否为有效的红黑树,注意:其内部主要依靠_IsValidRBTRee函数检测bool IsValidRBTRee();
private:bool _IsValidRBTRee(Node* pRoot, size_t blackCount, const size_t pathBlack);// 左单旋void RotateL(Node* pParent);// 右单旋void RotateR(Node* pParent);// 为了操作树简单起见:获取根节点Node*& GetRoot() const { return _root->_parent; }void _InOrder(Node* root);void rebalance(Node*& cur, Node*& parent)		//红黑树的平衡调整{while (parent != _root && parent->_col == RED){Node* grandParent = parent->_parent;if(parent == grandParent->_left){Node* uncle = grandParent->_right;if(uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandParent->_col = RED;cur = grandParent;parent = cur->_parent;}else {if(cur == parent->_left){   //右旋RotateR(grandParent);parent->_col = BLACK;grandParent->_col = RED;}else {   //双旋RotateL(parent);RotateR(grandParent);grandParent->_col = RED;cur->_col = BLACK;}break;}}else {Node* uncle = grandParent->_left;if(uncle && uncle->_col == BLACK){parent->_col = uncle->_col = BLACK;grandParent->_col = RED;cur = grandParent;parent = cur->_parent;}else {if(cur == parent->_right){RotateL(grandParent);parent->_col = BLACK;grandParent->_col = RED;}else {RotateR(parent);RotateL(grandParent);grandParent->_col = RED;cur->_col = BLACK;}break;}}}GetRoot()->_col = BLACK;}
private:Node* _root = nullptr;KeyOfT kot;
};template <class K, class T, class KeyOfT>
typename RBTree<K, T, KeyOfT>::const_iterator RBTree<K, T, KeyOfT>::Find(const K& data) const
{Node* cur = GetRoot();while(cur){if(kot(cur->_data) < data){cur = cur->_right;}else if(kot(cur->_data) > data){cur = cur->_left;}else {return cur;}}return nullptr;
}template <class K, class T, class KeyOfT>
typename RBTree<K, T, KeyOfT>::iterator RBTree<K, T, KeyOfT>::Find(const K& data) 
{Node* cur = GetRoot();while(cur){if(kot(cur->_data) < data){cur = cur->_right;}else if(kot(cur->_data) > data){cur = cur->_left;}else {return cur;}}return nullptr;
}template <class K, class T, class KeyOfT>
void RBTree<K, T, KeyOfT>::_InOrder(Node* root)	//中序遍历
{if(!root)   return;_InOrder(root->_left);std::cout << root->_data << " ";_InOrder(root->_right);
}template <class K, class T, class KeyOfT>
typename RBTree<K, T, KeyOfT>::Node* RBTree<K, T, KeyOfT>::LeftMost()const	//最左节点
{return _root->_left;
}template <class K, class T, class KeyOfT>
typename RBTree<K, T, KeyOfT>::Node* RBTree<K, T, KeyOfT>::RightMost()const	//最右节点
{return _root->_right;
}template <class K, class T, class KeyOfT>
bool RBTree<K, T, KeyOfT>::IsValidRBTRee()		//检查树的性质是否被破坏
{if(!GetRoot() || GetRoot()->_col == RED)  return false;size_t pathBlack = 0;Node* cur = GetRoot();while(cur){if(cur->_col == BLACK)++pathBlack;	//计算路径黑色节点的总个数cur = cur->_left;}int blackCount = 0;return _IsValidRBTRee(GetRoot(), blackCount, pathBlack);
}template <class K, class T, class KeyOfT>
bool RBTree<K, T, KeyOfT>::_IsValidRBTRee(Node* pRoot, size_t blackCount, const size_t pathBlack)
{if(!pRoot){if(blackCount != pathBlack){std::cout << "有连续的红色结点" << std::endl;return false;}return true;}if(pRoot->_col == RED && pRoot->_parent->_col == RED){std::cout << "有连续的红色结点" << std::endl;return false;}if(pRoot->_col == BLACK)++blackCount;return _IsValidRBTRee(pRoot->_left, blackCount, pathBlack)&& _IsValidRBTRee(pRoot->_right, blackCount, pathBlack);
}template <class K, class T, class KeyOfT>   
std::pair<typename RBTree<K, T, KeyOfT>::iterator, bool> RBTree<K, T, KeyOfT>::Insert(const T& data)
{if(GetRoot() == nullptr){Node* node = new Node(data);node->_col = BLACK;node->_parent = _root;_root->_parent = node;_root->_left = _root->_parent;_root->_right = _root->_parent;return std::make_pair(iterator(GetRoot()), true);}Node* cur = GetRoot();Node* parent = nullptr;while(cur){if(kot(cur->_data) < kot(data)){parent = cur;cur = cur->_right;}else if(kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}else return std::make_pair(iterator(cur), false);}cur = new Node(data);Node* ret = cur;		//记录新增的节点,因为在调整后,节点可能会丢失cur->_parent = parent;if(kot(cur->_data) < kot(parent->_data)){if (parent == _root->_left)	//更新最小节点_root->_left = cur;parent->_left = cur;}else {if(parent == _root->_right)	//更新最大节点_root->_right = cur;parent->_right = cur;}rebalance(cur, parent);return std::make_pair(ret, true);
}template <class K, class V, class KeyOfT>
void RBTree<K, V, KeyOfT>::RotateL(Node* parent)	//左旋
{Node* subR = parent->_right;Node* subRL = subR->_left;Node* parentParent = parent->_parent;parent->_right = subRL;parent->_parent = subR;subR->_parent = parentParent;subR->_left = parent;if(subRL)subRL->_parent = parent;if(GetRoot() == parent){GetRoot() = subR;}else {if(parent == parentParent->_left){parentParent->_left = subR;}else {parentParent->_right = subR;}}
}template <class K, class V, class KeyOfT>
void RBTree<K, V, KeyOfT>::RotateR(Node* parent)	//右旋
{Node* subL = parent->_left;Node* subLR = subL->_right;Node* parentParent = parent->_parent;subL->_right = parent;subL->_parent = parentParent;parent->_left = subLR;parent->_parent = subL;if(subLR)subLR->_parent = parent;if(parent == GetRoot())GetRoot() = subL;else{if(parent == parentParent->_left)parentParent->_left = subL;elseparentParent->_right = subL;}
} 

📓总结

📜博客主页:主页
📫我的专栏:C++
📱我的github:github

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java第十二篇:连接安全版kafka(Kerberos认证)出现的问题解答

Could not find a ‘KafkaClient’ entry in the JAAS configuration 问题现象 问题原因 原因没有找到&#xff0c;怎么引起的倒是很清楚。原因就是找到不到指定路径下的kafka_client_jaas.conf文件&#xff0c;别看我的路径带了两个//&#xff0c;但没问题的&#xff0c;等同…

想学计算机视觉入门的可以看过来了

文章写了有一段时间了&#xff0c;期间不少小伙伴来咨询如何自学入门AI&#xff0c;或者咨询一些AI算法。 90%的问题我都回复了&#xff0c;但有时确实因为太忙&#xff0c;没顾得过来。 在这个过程中&#xff0c;我发现很多小伙伴问的问题都类似&#xff1a;比如如何入门计算…

android13(T) 客制化预置语言列表

效果图 需求分析 这个列表界面一般都是后来手动添加后才现实的&#xff0c;通过分析源码发现通过如下值可控 adb shell settings get system system_locales zh-CN,ja-JP,en-AT 所以只需查询出这个值&#xff0c;然后加在 SettingProvider 中即可 隐藏 bug 如果客户要求默…

高防CDN可以起到什么作用?

高防CDN相对于普通的CDN加速&#xff0c;除了具备基础的加速功效外&#xff0c;高防CDN在每一节点上均有相应配置的防御功效&#xff0c;不仅具备了隐藏源站不被攻击的优势&#xff0c;也具备了访问加速&#xff0c;多节点防御的功效。随着互联网的不断发展&#xff0c;网络上的…

第二十章——多线程

一.线程简介 线程的特点 1.进程是资源分配的最小单位&#xff0c;线程是最小的执行单位 2.一个进程可以有多个线程 3.线程共享进程资源 二.创建线程 1.继承Thread类 1.Thread类是java.lang包中的一个类&#xff0c;从这个类实例化的对象代表线程&#xff0c;程序员启动一…

免交互语法expect

目录 前瞻 相关命令 范例一&#xff1a;免密登录另外一台主机并创建用户 范例二&#xff1a;免密登录另外三台主机并创建用户 前瞻 expect是建立在tcl&#xff08;tool command language&#xff09;语言基础上的一个工具&#xff0c;常被用于进行自动化控制和测试&#xf…

数据结构——链式二叉树的实现(详解)

呀哈喽。我是结衣。 不知道大家的递归学到怎么样呢&#xff1f;如果大家的递归功底不是很好&#xff0c;那么我相信在学完这篇文章后大家一定会对递归有一个更深层次的了解的。 构造链式二叉树 在学习二叉树的基本操作前&#xff0c;需先要创建一棵二叉树&#xff0c;然后才能…

常见气体爆炸下限

国标中关于常见气体爆炸下限说明&#xff0c;特此记录 特此记录 anlog 2023年11月29日

java编程:数组代表着苹果期货在 9 天内的价格变化,其中第 n 个元素是苹果在第 n 天的 价格,你可以进行最多三次交易,设计算法,9 天内赚到最多的钱

1 题目 编程题&#xff1a;下面的数组代表着苹果期货在 9 天内的价格变化&#xff0c;其中第 n 个元素是苹果在第 n 天的 价格&#xff0c;你可以进行最多三次交易&#xff0c;设计一个算法&#xff0c;9 天内如何赚到最多的钱&#xff1f;价格数组&#xff1a; [8, 9, 2, 5, …

面试必须要知道的MySQL知识--索引

10 索引 10.1 数据页存储结构 10.1.1 数据页的各个部分 在讲索引之前&#xff0c;让我们看看一个单独的数据页是什么样子的 去除掉一些我们不太需要那么关注的部分后&#xff0c;简化如下&#xff1a; 也就是说平时我们在一个表里插入的一行一行的数据会存储在数据页里&#…

Blender学习笔记:小车狂奔动画

文章目录 路旁小树汽车尾气移动 教程地址&#xff1a;八个案例教程带你从0到1入门blender【已完结】 小车建模 路旁小树 1 添加摄像机&#xff0c;在小车下面拉一个平面&#xff0c;覆盖到摄像机的观察视窗。复制一层平面&#xff0c;收窄变成小车两侧的路面&#xff0c;编辑…

VAE模型及pytorch实现

VAE模型及pytorch实现 VAE模型推导部分最小化KL散度推导代码部分损失函数Encoder部分Decoder部分VAE整体架构 VAE问题参考资料 VAE&#xff08;变分自编码器&#xff09;是一种生成模型&#xff0c;结合了自编码器和概率图模型的思想。它通过学习数据的潜在分布&#xff0c;可以…