在学习红黑树之前先看这篇文章
红黑树和2-3树之间的联系
红黑树:是一颗平衡二叉搜索树,给书中的结点增加颜色(红色和黑色) + 红黑树性质的限制
保证:最长路径中结点个数不会超过最短路径中结点个数的两倍
不像AVL树觉得平衡
红黑树是一颗近似平衡的二叉搜索树---->时间复杂度:O(logN)
大量的应用表明:红黑树的性质比AVL树的性能好,并且实现起来也比红黑树简单一些,所以在很多地方我们使用的其实是红黑树
**红黑树的概念:**红黑树,是一种二叉搜索树,但在每个节点上增加一个存储位表示节点的颜色,可以是Red或Black.通过对任何一条从根到叶子节点的路径会比其他路径长出两倍,因为是接近平衡的。
红黑树的性质
1.每个结点不是红色就是黑色
2.根结点是黑色
3.如果一个结点是红色的,则它的两个孩子结点是黑色的
4.对于每个结点,从该节点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
5.每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
红黑树中不存在连在一起的红色结点,但是可以有黑色结点连在一起
通过颜色以及性质的限定:最长路径中结点个数不会超过最短路径中结点个数的两倍。
一般插入的时候新插入的结点都是红色的,这是为什么呢?
假如有下图这样一个黑色根节点
现在要插入一个新节点,假如是黑色的那就违反了四号性质,也就是说有一条路径有2个黑色结点,另一个路径只有一个黑色结点,所以新插入的结点一般都给成红色。
也就是说,下面这颗红黑树是不可能存在的
认为红黑树是一个近似平衡的二叉搜索树
在给红黑树中插入一个结点的时候到底是给成红色好,还是给成黑色好?
关于这个问题我们可以来假设一下,
按道理来说给成红色还是黑色都是可以的,因为本来插入的过程就是一个不断调整的过程,在插入之前整个红黑树已经平衡了,假如你插入的是一个黑色结点,那么肯定会破坏红黑树的性质,也就是说又要继续调整,并且是必学的,假如你插入的是红的结点的话,那么就有可能不需要调整,所以一般来说的花,我们一般选择默认插入的是红色结点。
接着我们就试着自己封装简单的红黑树:
首先我们来认识一下什么是空的红黑树
有关红黑树的插入:
该如何来调整节点的颜色呢?
将p结点设置为黑色,将u结点设置为黑色,双亲g改为红色,然后cur代替p,p代替g,继续向上更新。
情况2:
上面讨论的这三种情况都是p节点是g结点的左孩子,当然也可以是右孩子,反过来又是三种情况,这里就不详细介绍了。
//简单实现
#include<iostream>
#include<vector>
using namespace std;
namespace mytree
{
enum color{ RED, BLACK };
template<class T>
struct RBTreeNode
{
RBTreeNode(const T& val = T(), color color = RED)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _val(val)
, _color(color)
{
}
RBTreeNode* _left;
RBTreeNode* _right;
RBTreeNode* _parent;
T _val;
color _color;
};
//假设红黑树中的值是唯一的
template<class T>
class RBTree
{
typedef RBTreeNode<T> Node;
public:
RBTree() //创建头节点
{
pHead = new Node;
pHead->_left = pHead;
pHead->_right = pHead;
pHead->_parent = nullptr;
size = 0;
}
bool insert(const T& data)
{
Node*& pRoot = getRoot();
if (nullptr == pRoot) //假如现在只有一个头节点
{
pRoot = new Node(val, BLACK);
pHead->_left = pRoot;
pHead->_right = pRoot;
pHead->_parent = pHead;
return true;
}
//非空
//按照二叉搜索树的规律插入新节点
Node* pCur = pRoot;
Node* pParent = nullptr;
while (pCur)
{
pParent = pCur;
if (val < pCur->_val)
{
pCur = pCur->_left;
}
else if (val > pCur->_val)
{
pCur = pCur->_right;
}
else
{
//val对应结点已经存在
return false;
}
//插入新节点
pCur = new Node(val);
if (val < pParent->_val)
pParent->_left = pCur;
else
pParent->_right = pCur;
pCur->_parent = pParent;
//检测插入是否违反红黑树的性质
//只需要检测一下性质三
//新节点的默颜色是红色,如果新节点的双亲节点也是红色,必定会违反性质3
while (pParent != pHead && RED == pParent->_color)
{
Node* grandFather = pParent->_parent;
if (pParent == grandFather->_left) //p节点是祖父节点的左孩子三种情况
{
//上面讨论的三种情况
Node* uncle = grandFather->_right;
if (uncle && RED = uncle->_color)
{
//情况1 :叔叔结点存在且为红色
pParent->_color = BLACK;
uncle->_color = BLACK;
grandFather->_color = RED;
//因为祖父有双亲,所以需要继续向上调整
pCur = grandFather;
pParent = pCur->_parent;
}
else
{
//情况二和情况三
//先处理情况三
if (pCur == pParent->_right)
{
//对以pParent为根的二叉树进行左单旋
RotateLeft(pParent);
swap(pParent, pCur);
}
//交换双亲和祖父的颜色
grandFather->_color = RED;
pParent->_color = BLACK;
RotateRight(grandFather);
}
}
else //是右孩子的三种情况
{
Node* uncle = grandFather->_left;
if (uncle && RED == uncle->_color)
{
//情况一的反情况
pParent->_color = BLACK;
pCur->_color = BLACK;
grandFather->_color = RED;
pCur = grandFather;
pParent = pCur->_parent;
}
else
{
//情况二和情况三的反情况
if (pCur == pParent->_left)
{
RotateRight(pParent);
swap(pCur, pParent);
}
pParent->_color = BLACK;
grandFather->_color = RED;
RotateLeft(grandFather);
}
}
}
//更新pHead的左右指针域
pRoot->_color = BLACK;
pHead->_left = LeftMost();
pHead->_right = RightMost();
size++;
return true;
}
}
private:
void RotateLeft(Node* pParent) //左单选
{
Node* pSubR = pParent->_right;
Node* pSubRL = pSubR->_left;
pParent->_right = pSubRL;
if (pSubRL)
pSubRL->_parent = pParent;
pSubR->_left = pParent;
Node* pPParent = pParent->_parent;
pParent->_parent = pSubR;
pSubR->_parent = pPParent;
if (pPParent == pHead)
{
//pParent就是根节点
pHead->_parent = pSubR;
}
else
{
if (pParent == pPParent->_left)
pPParent->_left = pSubR;
else
pPParent->_right = pSubR;
}
}
void Rotateright(Node* pParent) //右单旋
{
Node* pSubL = pParent->_left;
Node* pSubLR = pSubL->_right;
pParent->_left = pSubLR;
if (pSubLR)
pSubLR->_parent = pParent;
pSubR->_right = pParent;
Node* pPParent = pParent->_parent;
pSubL->_parent = pPParent;
pParent->_parent = pSubL;
if (pPParent == pHead)
{
//pParent就是根节点
pHead->_parent = pSubL;
}
else
{
if (pParent == pPParent->_left)
pPParent->_right = pSubL;
else
pPParent->_left = pSubL;
}
}
Node* LeftMost()
{
Node* pRoot = getRoot();
if (nullptr == pRoot)
return pHead;
Node* pCur = pRoot;
while (pCur->_left)
pCur = pCur->_left;
return pCur;
}
Node* RightMost()
{
Node* pRoot = getRoot();
if (nullptr == pRoot)
return pHead;
Node* pCur = pRoot;
while (pCur->_right)
pCur = pCur->_right;
return pCur;
}
Node*& gerRoot()
{
return pHead->_parent;
}
Node* pHead;
size_t size;
};
};
树形结构 map / set / multimap / multiset
相同点:
1.STL标准库提供的关联式容器
2.进行中序遍历可以得到有序序列—>默认情况下升序
3.模板参数列表是按照less小于来比较K的,如果将来插入自定义类型的元素需要用户自己提供比较的方式(仿函数)
4.查询的时间复杂度:O(logN)
5.底层应用好的都是红黑树
6.迭代器移动的方式:都是按照中序遍历的方式进行移动的
不同点:
map:存储的是K-V键值对,要求K必须要唯一。
set:只存储了K,要i去K必须唯一
multimap:存储的是K-V键值对,K可以重复
mutliset:只存储了K,并且K可以重复
作用:
map:按照key进行去重 + 排序
set的作用:去重和对去重之后的结果进行排序。
multimap:按照key进行排序
multiset:排序
map/multimap在实例化的时候:必须提供K-V的类型 + map头文件& 命名空间
set/multiset在实例化的时候只需要提供K的类型 + set头文件 & 命名空间
接口上:大部分接口都是一样的,map多了一个根据key取value的操作:operator[ ] (key)
如果查找 操作比较多,那就用unordered系列的,如果需要有序就使用树形结构