1 二叉搜索树
二叉搜索树 又叫二叉排序树
特点
1: 左子树上的所有节点小于 根节点的值,右子树上的所有节点大于根节点的值。
2 空树也是二叉搜索树。
3 二叉搜索树不存在值相等的节点。
4 二叉搜索树的左右子树也分别是二叉搜索树。
eg:
二叉搜索树的操作 :
1 查找:
根据二叉搜索树的特点,可以进行判断
大于根,就在右子树中找,
小于根 ,左子树中找。
2 插入
1 树为空 ,直接插入。
2 不为空,找自己插入的位置 ,若已存在,插入失败。
二叉排序树的插入一定是在叶子节点上进行的。
3 删除
删除分情况 :
a : 若删除的节点不存在,则返回失败,
b :删除的节点无左子树,将孩子连给父亲
c : 删除的节点没有右子树,将孩子连给父亲
d: 既有左子树,又有右子树,直接删不好删,**我们采用替代删除的思想,**
即 要不然删除该节点左子树中最大的-----即左子树中中序遍历最后一个节点,或者,右子树中最小的,
做法:先找到一个替代节点,将该节点的值赋给原来要删的节点,再删掉替代结点就行。
4 二叉搜索树的模拟实现
#include <stdio.h>
#include <iostream>
#include <stack>
//结点的组成
template <class T>//类似于模板类 ,模板结构体
struct BSTNode
{
BSTNode(const T& data)
:pLeft_(nullptr)
,pRight_(nullptr)
,pParent_(nullptr)
,data_(data)
,buf_(0)
{}
BSTNode<T>* pLeft_;
BSTNode<T>* pRight_;
BSTNode<T>* pParent_;
T data_;
int buf_;//平衡因子
};
template <class T>
class BSTree
{
typedef BSTNode<T> Node;//取别名
typedef Node* PNode;//结点指针
public:
BSTree()
:pNode_(nullptr)
{}
//后序递归销毁
void Destroy(PNode pRoot_)
{
if(pRoot_->pLeft_ == nullptr && pRoot_->pRight_ == nullptr)
{
free(pRoot_);
pRoot_ = nullptr;//这一步还是很重要要的,1避免出现野指针,2 使递归回归时能正常进行
return ;
}
if(pRoot_->pLeft_)
Destroy(pRoot_ ->pLeft_);
if(pRoot_ ->pRight_)
Destroy(pRoot_->pRight_);
}
~BSTree()
{
Destroy();
}
//用二叉搜索树的特性查找 data 元素 ,找到返回位置指针 ,未找到 返回nullptr
//Find1 递归 //数据大 ,性能差
PNode Find1(const T& data)const
{
if(pNode_ == nullptr)
{
return nullptr;
}
PNode tmp =pNode_;
if(tmp->data_ == data)
{
return tmp;
}
else if(tmp->pLeft_ ==nullptr && tmp->pRight_ == nullptr)
{
return nullptr;
}
else if(tmp->pLeft_ == nullptr && tmp->data_ >data )
{
return nullptr;
}
else if(tmp->pRight_ == nullptr && tmp->data_ < data)
{
return nullptr;
}
else if(data > pNode_->data_)
{
tmp = tmp->pRight_;
Find (data);
}
else
{
tmp = tmp ->pLeft_;
Find(data);
}
}
//Find 非递归
PNode Find(const T& data)
{
if (pNode_ == nullptr)
return nullptr;
PNode cur = pNode_;
while(cur)
{
if(cur->data_ == data )
return cur;
else if(cur-> data_ > data)
{
cur = cur->pRight_ ;
}
else
{
cur = cur ->pLeft_;
}
}
return cur;
}
bool insert(const T& data)
{
//1 空树
if(pNode_ ==nullptr)
{
pNode_ = new PNode(data);
return true;
}
PNode parent = nullptr;//原来红黑树中不存在data, 就要插入data,并和原树 关联
PNode cur =pNode_;
//2 比根小
while(cur)//一直一直找 ,找到当前树为空的情况
{
parent = cur;//提前保存双亲的位置
if(data < cur->data_)
{
cur = cur->pLeft_;
}
else if(data > cur->data_)
{
cur = cur->pRight_;
}
else//说明存在
{
return false;
}
}
//走到这里说明data不存在 ,找到了自己该待的位置了
PNode tmp = new PNode(data);
//给自己找双亲
if(data > parent->data_)
parent->pRight_ = tmp;
else
parent->pLeft_ = tmp;
return true;
}
bool erase(const T& data)
{
PNode ret = Find(data);
if(ret == nullptr)
{
perror("不存在 data");
return false;
}
if(ret->pLeft_ == nullptr && ret->pRight_ ==nullptr)//只有一个根节点
{
delete ret;
return true;
}
else if(ret ->pLeft_ == nullptr)//左子树为空
{
PNode tmp = ret->pRight_;
delete ret;
ret =tmp;
return true;
}
else if(ret->pRight_ == nullptr) //右子树为空
{
PNode tmp = ret ->pLeft_;
delete ret;
ret = tmp;
return true;
}
//左右子树都存在,直接删,不好删,需要调整好多结点的位置,换种思想 删除替代结点的方式---删除左子树最大的结点,先交换值
//或者 删除右子树最小的结点 ,也需要先交换值
else //左右子树都存在
{
PNode tmp = ret->pLeft_;
PNode parent = nullptr;
while(tmp)
{
parent = tmp;
if(tmp->pRight_)
tmp = tmp->pRight_;
else
tmp = tmp->pLeft_;
}
ret->data_ = parent->data_;
delete parent;
return true;
}
}
//中序遍历
void Inorder( PNode& pNode_ )const
{
PNode cur = pNode_;
if( cur == nullptr)
return ;
Inorder(pNode_->pLeft_);
std::cout<<cur->data_<<" ";
Inorder(pNode_->pRight_);
}
//非递归
void Inorder2()
{
std::stack<std::pair<PNode,bool> >s;
s.push(make_pair(pNode_,false));//相当于 用 true 标记了 每一层的根节点
while(!s.empty())
{
PNode tmp = s.top().first;
bool ret = s.top().second;
s.pop();
if(tmp == nullptr)//当前结点为空 ,则跳过
{
continue;
}
if(ret)//查看标记
{
std::cout<<tmp->data_<<" ";
}
else//注意栈 的 特性
{
s.push(make_pair(tmp->pRight_,false));
s.push(make_pair(tmp,true));//根结点用 true 标记
s.push(make_pair(tmp->pLeft_,false));
}
}
}
5 二叉排序树的性能分析
二叉搜索树的 插入 和 删除操作都需要进行查找,,所以查找的效率代表了二叉搜索树的各个操作的效率。
对于一个关键码集合,如果个个关键码插入的次序 不相同,那么得到的二叉搜索树 也不一样
最优情况下,二叉搜索树为完全二叉树Log N/2
最坏情况下 退化为单支树,N/2
为了避免形成单支树 使性能最差,我们采用引入AVL树
AVL树
什么是AVL 树
1 首先是一颗二叉树
2 其次 左右子树的高度差(平衡因子)不超过1
3 左右子树也必须是AVL树
AVL树是高度平衡的,所以搜索的时间复杂度为O(log2 N)
AVL树的插入 :
像二叉搜索树的插入一样 ,可以进行插入。只不过在二叉搜索树的基础上 增加了平衡因子。
插入完成后,肯能会导致原树的平衡因子发生变化,所以我们插入后可能需要 进行调整平衡因子。
插入后 平衡因子的三种情况: 0 -1 1 2 -2
a 当平衡因子为0 时。不用调整,
b 当平衡因子为1 或 -1 时,需要向上调整,
c 当平衡因子为 -2 或 2 时,只可能出现在插入d 节点的爷爷节点位置,此时进行响应调整 。
调整方案: 旋转
需要进行调整的情况: 平衡因子为2 或 -2 时
四种情况:
均以 插入在 2 或 -2 平衡因子节点的相对位置而言:
a:左子树的左子树 ---- 右旋
b:右子树的右子树 ---- 左旋
c:左子树的右子树 ---- 先左旋,再右旋
d:右子树的左子树 ---- 先右旋,在左旋
四种情况下对应四种调整方案:
1 左旋 :(根从左边旋过来)
2 右旋 : (根从右边旋过来)
3 先左旋,再右旋
4 先右旋,再左旋
注意:
旋转处理完毕后 ,根据 插入节点的父节点的之前的情况 调整 父节点的爷爷节点 和 父节点的父节点父节点的 平衡因子。
AVL树的删除:类似于平衡二叉树的删除,只不过多了调节平衡因子, ,但删除节点后平衡因子的更新,最坏情况下一直要调整到根节点。
实现:
#include <stdio.h>
#include <iostream>
#include <stack>
//结点的组成
template <class T>//类似于模板类 ,模板结构体
struct BSTNode
{
BSTNode(const T& data)
:pLeft_(nullptr)
,pRight_(nullptr)
,pParent_(nullptr)
,data_(data)
,buf_(0)
{}
BSTNode<T>* pLeft_;
BSTNode<T>* pRight_;
BSTNode<T>* pParent_;
T data_;
int buf_;//平衡因子
};
template <class T>
class BSTree
{
typedef BSTNode<T> Node;//取别名
typedef Node* PNode;//结点指针
public:
BSTree()
:pNode_(nullptr)
{}
//后序递归销毁
void Destroy(PNode pRoot_)
{
if(pRoot_->pLeft_ == nullptr && pRoot_->pRight_ == nullptr)
{
free(pRoot_);
pRoot_ = nullptr;//这一步还是很重要要的,1避免出现野指针,2 使递归回归时能正常进行
return ;
}
if(pRoot_->pLeft_)
Destroy(pRoot_ ->pLeft_);
if(pRoot_ ->pRight_)
Destroy(pRoot_->pRight_);
}
~BSTree()
{
Destroy();
}
//用二叉搜索树的特性查找 data 元素 ,找到返回位置指针 ,未找到 返回nullptr
//Find1 递归 //数据大 ,性能差
PNode Find1(const T& data)const
{
if(pNode_ == nullptr)
{
return nullptr;
}
PNode tmp =pNode_;
if(tmp->data_ == data)
{
return tmp;
}
else if(tmp->pLeft_ ==nullptr && tmp->pRight_ == nullptr)
{
return nullptr;
}
else if(tmp->pLeft_ == nullptr && tmp->data_ >data )
{
return nullptr;
}
else if(tmp->pRight_ == nullptr && tmp->data_ < data)
{
return nullptr;
}
else if(data > pNode_->data_)
{
tmp = tmp->pRight_;
Find (data);
}
else
{
tmp = tmp ->pLeft_;
Find(data);
}
}
//Find 非递归
PNode Find(const T& data)
{
if (pNode_ == nullptr)
return nullptr;
PNode cur = pNode_;
while(cur)
{
if(cur->data_ == data )
return cur;
else if(cur-> data_ > data)
{
cur = cur->pRight_ ;
}
else
{
cur = cur ->pLeft_;
}
}
return cur;
}
//我们 为了容器而实现红黑树 ,所以保证接口一至 bool
bool insert(const T& data)
{
//1 空树
if(pNode_ ==nullptr)
{
pNode_ = new PNode(data);
return true;
}
PNode parent = nullptr;//原来红黑树中不存在data, 就要插入data,并和原树 关联
PNode cur =pNode_;
//2 比根小
while(cur)//一直一直找 ,找到当前树为空的情况
{
parent = cur;//提前保存双亲的位置
if(data < cur->data_)
{
cur = cur->pLeft_;
}
else if(data > cur->data_)
{
cur = cur->pRight_;
}
else//说明存在
{
return false;
}
}
//走到这里说明data不存在 ,找到了自己该待的位置了
PNode tmp = new PNode(data);
//给自己找双亲
if(data > parent->data_)
parent->pRight_ = tmp;
else
parent->pLeft_ = tmp;
return true;
}
bool erase(const T& data)
{
PNode ret = Find(data);
if(ret == nullptr)
{
perror("不存在 data");
return false;
}
if(ret->pLeft_ == nullptr && ret->pRight_ ==nullptr)//只有一个根节点
{
delete ret;
return true;
}
else if(ret ->pLeft_ == nullptr)//左子树为空
{
PNode tmp = ret->pRight_;
delete ret;
ret =tmp;
return true;
}
else if(ret->pRight_ == nullptr) //右子树为空
{
PNode tmp = ret ->pLeft_;
delete ret;
ret = tmp;
return true;
}
//左右子树都存在,直接删,不好删,需要调整好多结点的位置,换种思想 删除替代结点的方式---删除左子树最大的结点,先交换值
//或者 删除右子树最小的结点 ,也需要先交换值
else //左右子树都存在
{
PNode tmp = ret->pLeft_;
PNode parent = nullptr;
while(tmp)
{
parent = tmp;
if(tmp->pRight_)
tmp = tmp->pRight_;
else
tmp = tmp->pLeft_;
}
ret->data_ = parent->data_;
delete parent;
return true;
}
}
//中序遍历
void Inorder( PNode& pNode_ )const
{
PNode cur = pNode_;
if( cur == nullptr)
return ;
Inorder(pNode_->pLeft_);
std::cout<<cur->data_<<" ";
Inorder(pNode_->pRight_);
}
//非递归
void Inorder2()
{
std::stack<std::pair<PNode,bool> >s;
s.push(make_pair(pNode_,false));//相当于 用 true 标记了 每一层的根节点
while(!s.empty())
{
PNode tmp = s.top().first;
bool ret = s.top().second;
s.pop();
if(tmp == nullptr)//当前结点为空 ,则跳过
{
continue;
}
if(ret)//查看标记
{
std::cout<<tmp->data_<<" ";
}
else//注意栈 的 特性
{
s.push(make_pair(tmp->pRight_,false));
s.push(make_pair(tmp,true));//根结点用 true 标记
s.push(make_pair(tmp->pLeft_,false));
}
}
}
//四种情况需要 旋转处理
//1 左左 右单旋
void rotateR(PNode parent)
{
//插入左左 ,旋转 左右
PNode subL = parent->pLeft_;//备份 左子树
PNode subLR = subL->pRight_; // 备份 左子树的右子树
parent->pLeft_ = subLR;// 1 .将左孩子的右孩子赋值给 根的左孩子
if(subLR != nullptr)//
{
subLR->pParent_ = parent;// 更新他的父节点指向
}
subL->pRight_ = parent; // 2 将根剪切到subLR的位置
parent->pParent_ = subL;//更新 parent 当前的父节点
PNode ppParent = parent->pParent_;//原来的父节点
subL->pParent_ = ppParent; // 更新父节点
if(ppParent == nullptr)// 3 若 parent 结点为根节点
{
pNode_ = subL;
subL->pParent_ = nullptr; // 更新父节点
}
// 4 parent 非根,更新其父节点的相应子树指针
else//不是根,parent可能是他栓亲的左子树 , 也可能是双亲的右子树
{
if(ppParent->pLeft_ == parent )//是双亲的左孩子
ppParent->pLeft_ = subL;
else
ppParent ->pRight_ = subL;
}
parent->buf_ = subL->buf_ = 0;//当亲调整后平衡因子为0
}
//2 右右 ---- 左单旋
void rotateL(PNode parent)
{
//插入右右 ,旋转右左
PNode subR = parent->pLeft_;
PNode subRL = subR->pLeft_;
parent ->pLeft_ = subRL;
if(subRL != nullptr)
{
subRL->pParent_ = parent;
}
subR->pLeft_ = parent;
parent->pParent_ = subR;
PNode ppParent = parent->pParent_;
subR->pParent_ = ppParent;
if(ppParent == nullptr)//parent 为根
{
pNode_ = subR;
subR->pParent_ = nullptr;
}
else // parant 不为根结点
{
if(parent == ppParent->pLeft_)
{
ppParent->pLeft_ = subR;
}
else
{
ppParent ->pRight_ = subR;
}
}
}
//3 插入较高左子树的右子树 先左单旋 ,再右单旋
void rotateLR(PNode parent)
{
PNode subL = parent->pLeft_;
PNode subLR = subL->pRight_;
int buf = subLR->buf_;
rotateL(parent->pLeft_);
rotateR(parent);
//根据插入后 subLR的 平衡因子 调整其他平衡因子 ,变为0 的平衡因子不用处理,我们在后序函数中统一处理
if(buf == 1)// subLR 右边高,
{
subL->buf_ = -1;//旋转之后, subLR 称为 sub 的左子树
}
else if(buf == -1)
{
parent->buf_ = 1;
}
else // 插入后 平衡因子是0 //不存在,既然需要旋转处理 ,那一定是平衡因子出问题 ,而且只能是插入结点的爷爷结点为平衡因子为+2 / -2,
// 那么插入节点的父节点平衡因子== +1 / -1 //因我们处理平衡因子 是从底向上处理的
{ ; }
}
//4 插入到较高右子树的左子树 --- 右左 先 右旋转 ,再左旋转
void rotateRL(PNode parent)
{
PNode subR = parent->pRight_;
PNode subRL = subR->pLeft_;
int buf = subRL ->buf_;
rotateR(parent->pRight_);
rotateL(parent);
if(buf == 1)
{
parent->buf_ = -1;
}
else if(buf == -1)
{
subR->buf_ = 1;
}
}
// 原理 AVL树的插入原理
// 1 像二叉搜索树那样 正常插入
// 2 插入成功之后 可能会导致原有节点的 平衡因子 发生变化 ,所以要进行调整平衡因子
// 3 插入后平衡因子 可能 会有 0 -1 1 2 -2 这几种情况
// ,若为 0 则正常 退出 ,
// 若为1 -1 则进行向上调整平衡因子
// 若为 2 -2 说明 该问题一定发生在 爷爷节点上 ,进行旋转处理
bool Insert(const T& data)
{
int ret = insert(data);
if(ret == false)
{
std::cout<<"data 已存在"<<std::endl;
return true;
}
// 否则插入成功
PNode cur = Find(data);
PNode parent = cur->pParent_;
while(parent)
{
//出入到 父节点哪边,平衡因子做相应的调整
if(cur == parent->pLeft_ )
--(parent->buf_);
else
++(parent->buf_);
if(parent->buf_ == 0)//正常
{
break;//一切正常 , 原有所有的节点的平衡因子都不用处理, 结束
}
else if(parent ->buf_ == -1 || parent->buf_ == 1)//说明 插入前双亲为根的二叉树平衡因子 0 ,插入后 多了一层,因此需要向上调整
{
parent = parent->pParent_;
}
// 平衡因子为 2 或者 -2 只可能出现在 插入节点的 爷爷节点上
else if(parent ->buf == 2)//插入到了右边,需要调整 ,接着判断 1 右旋 ? 2 先右旋再左旋?
{
if(cur == (parent->pRight_)->pRight_)//插入 右右 ,右旋转
rotateL(parent);
else // 插入 右 左 先右旋转 ,再左旋转
rotateRL(parent);
//继续向上调整
parent = parent->pParent_;
}
else // 只可能是 -2, 判断 左旋 还是 先左 后右
{
//插入到左子树的左子树 右旋转
if(cur == (parent->pLeft_)->pLeft_)
rotateR(parent);
else // 插入到左子树的右子树 先 左旋转 再右旋转
rotateLR(parent);
//继续向上调整
parent = parent->pParent_;
}
}
}
private:
PNode pNode_;
};
AVL树的性能方面:
AVL树是严格的平衡二叉排序树,所以查找效率非常高 O(log 2 N),
但对AVL树进行修改时,性能非常差,
插入时,为了维护其绝对平衡 ,就可能进行多次旋转。
最差的情况是在删除时, 可能一直要让旋转持续到根节点的位置。
红黑树
红黑树是一种自平衡二叉查找树
所谓自平衡 ,就是说明 红黑树不一定是AVL树,红黑树自平衡,(从根到叶子,黑色节点数相同)
为什么 学习红黑树?
因为效率问题 :(当然这是后话)可以参考各自的优缺点。
红黑树和AVL树的优缺点
1 如果插入一个节点引起了树的不平衡,AVL树和红黑树都最多两次即可解决问题。 但在删除一个节点引起树的不平衡,最坏情况下,AVL树需要从最深的不平衡节点开始一直调整,直到调整到这条路径所有节点均平衡,即AVL树需要若干次旋转调整。而红黑树最多需要三次旋转即可自平衡。
2 AVL树是高度平衡的二叉搜索树,因此AVL树的查找效率能比红黑树快一丢丢,*(最多快一次),因为红黑树没有AVL树那样高度平衡,但最多只是稍不平衡多出1层,也就意味着,红黑树比AVL树查找的次数最多多一次。
3 AVL树在大量数据插入和删除时,调整的次数比红黑树要多,因此,红黑树在大量数据的插入和删除时效率更高。
相对AVL树 ,红黑树牺牲了部分平衡性,换取了插入和删除时所导致旋转的 效率问题的解决。
红黑树的特点
1 所有的节点不是红色就是黑色(废话嘛)
2 根是黑色的
3 所有的 空叶子 都为黑色(废话)
4 红色的节点的子节点必须是黑色的。
5 从根节点到任意一个叶子节点,黑色节点的数目是 相等的。
总之 1 红色节点不能相邻
2 根节点是黑色
3 从根节点到任意叶子节点,黑色节点数相等
默认 创建一颗红黑树所有节点都是红色
红黑树的插入
请注意: 每种情形下还有对称的一面
情形一 :树为空树
直接插入 做根,颜色改为黑色。
情形二 :插入位置的父节点为黑色,直接插入即可。
情形三:当该插入位置 节点的的父节点和 叔叔节点都是红色:(即颜色对称)
如下 : 插入 24
处理方式:将叔叔 节点 和 父节点都改为黑色,爷爷节点改为红色 。然后递归处理,直到处理完根节点(必须保证根节点为黑色)
处理原则 :1 两个红色不能相邻,
2 时刻注意父节点和叔叔节点的颜色。
若父亲和自己同为红色,父亲和叔叔也同为红色,则将父亲和叔叔改色,爷爷也改色。(根节点颜色不变)
**情形四 :
1 叔叔和父亲不同色,
2 自己是父亲的右子树,父亲是爷爷的左子树 (对称情况: 自己是父亲的左子树,父亲是爷爷的右子树) **
处理方式:对父亲进行旋转,处理成情形五的样子。
非左旋即右旋,视情况而定。
此时情况就成了情形五。
情形五:
1 父亲叔叔不同色。
2 自己是父亲的左子树,父亲是爷爷的左子树。 (对称情况: 自己是父亲的右子树,父亲是爷爷的右子树。)
处理方式:视具体情况进行旋转,并改色。
红黑树 只有两种旋转:
1 左旋,
2 右旋,
注意:1 为了后续实现关联式容器,红黑树的实现中增加一个头节点,Head节点 ,Root的双亲
2 为了区分Head节点与根节点 ,将Head的双亲指向Root 根节点 ,同时,将Head头节点的做孩子指向Root最左侧的,即最小的节点,Head的右节点指向Root最右侧的节点,即最大的节点。
红黑树的插入操作
红黑树的插入操作 根二叉搜索树一致,但不同的是,插入新节点后,可能毁坏红黑树的自平衡。因此 ,插入完成后,还可能需要进行相应的旋转 + 改色
实现
#include <iostream>
enum Color{RED, BLACK};
template <class T>
struct RBTreeNode
{
RBTreeNode(const T& data = T() , Color color = RED)
:pLeft_(nullptr)
,pRight_(nullptr)
,pParent_(nullptr)
,data_(data)//平衡因子
,color_(color)//默认是红色结点 ,因为黑色节点必然会影响自平衡
{}
RBTreeNode<T> * pLeft_;
RBTreeNode<T> * pRight_;
RBTreeNode<T> * pParent_;
T data_;
Color color_;
};
// 红黑树实在二叉搜索树的基础上增加 平衡条件, 因此 红黑树的插入分两步, 1 二叉搜索树的插入 , 2 平衡调整
//注意 ,为了实现后续的关联式容器 ,我们在红黑树的实现中加上了 Head_ 头节点 Root->pParent_ == Head
template <class T>
class RBTree
{
typedef RBTreeNode<T> Node;
typedef Node* PNode;
public:
//右旋
//我这里命名不科学,切勿自带感情 ,参数grandpa 最好用 pNode 代替
void rotateR1(PNode& grandpa)
{
PNode ggrandpa = grandpa->pParent_;//爷爷的父亲
PNode parent = grandpa->pLeft_;
PNode uncle = grandpa ->pRight_;
grandpa->pLeft_ = parent->pRight_;
parent->pRight_= grandpa;
parent->pParent_ = ggrandpa;
grandpa->pParent_ = parent;
//更改 ggrandpa 的指向
// 3 种情况 ggrandpa 为 head 2 不为head ,但是grandpa为其左子树 3 为其右子树
if(grandpa == ggrandpa->pLeft_)
parent = ggrandpa->pLeft_;
else if(grandpa == ggrandpa ->pRight_)
parent = ggrandpa->pRight_;
else
parent = ggrandpa->pParent_;
}
void rotateR(PNode& pNode)//参数为要被旋转的结点
{
PNode parent = pNode->pParent_;//要旋转节点的父节点
PNode newHead = pNode->pLeft_;
pNode->pLeft_ = newHead->right;
newHead->pRight_ = pNode;
pNode->pParent_ = newHead;
newHead->pParent_ = parent;
if(pNode == parent->pLeft_)
parent->pLeft_ = newHead;
else if(pNode == parent ->pRight_)
parent->pRight_ = newHead;
else
parent ->pParent_ = newHead;
}
//左旋
void rotateL(PNode& pNode)
{
PNode parent =pNode;
PNode cur = pNode->pRight_;
PNode grandpa = parent->pParent_;
parent->pRight_ = cur->pLeft_;
cur->pLeft_ = parent;
cur->pParent_ = parent->pParent_;
parent->pParent_ = cur;
if(grandpa->pLeft_ == grandpa)
grandpa->pLeft_ = parent;
else if(grandpa->pRight_ == grandpa)
grandpa->pRight_ = parent;
else
grandpa->pParent_ = parent;
}
//下边这种写法不好
void rotateL1(PNode grandpa)
{
PNode ggrandpa = grandpa->pParent_;//爷爷的父亲
PNode parent = grandpa->pLeft_;
PNode uncle = grandpa ->pRight_;
grandpa->pRight_ = parent->pLeft_;
parent->pLeft_ = grandpa;
parent->pParent_ = ggrandpa;
grandpa->pParent_ = parent;
if(grandpa == ggrandpa->pLeft_)
parent = ggrandpa->pLeft_;
else if(grandpa == ggrandpa ->pRight_)
parent = ggrandpa->pRight_;
else
parent = ggrandpa->pParent_;
}
bool Insert(const T& data)
{
PNode pRoot = GetRoot();
//双亲为空
if(pRoot == nullptr) //空树
{
pRoot = new RBTree (data,RED);
pRoot ->pParent_ = pHead_;
pHead_->pParent_ = pRoot;
}
else//不是空树
{
//1 像二叉搜索树那样 先插入 ,
//2 可能进行相应的旋转,调色
PNode cur = pRoot;
PNode parent = nullptr;
while(cur)
{
parent = cur;//保存双亲,方便后期插入节点时进行连接
if(cur->data_ > data)
{
cur =cur ->pLeft_ ;
}
else if(cur->data_ < data)
{
cur = cur->pRight_;
}
else
{
std::cout<<"data 已存在,插入失败"<<std::endl;
return false;
}
}
//cur == nullptr 即这里就是应该插入的位置 插入节点 并进行链接
cur = new PNode (data,RED);
cur->pParent_ = parent;
if(cur->data_ < parent->data_)
cur = parent->pLeft_;
else
cur = parent->pRight_;
//插入节点完成, 进行判断是否需要 旋转 + 改色 调整
//1 parent 不存在,即 空树
//直接处理跟颜色即可
//2 parent 存在 ,parent 为黑色
//直接插入,不需要 调整
//3 parent 存在(即不是空树) 并且cur父亲为红色,叔叔为红色
//4 parent 存在 ,叔父不同色, ----------直线型
//1 自己是父亲的左子树, 父亲是爷爷的左子树,
//2 自己是父亲的右子树, 父亲是爷爷的右子树,
//5 parent 存在 , 叔父不同色 ---------- √型
//1 ,自己是父亲的右子树, 父亲是爷爷的左子树
//2 自己是父亲的左子树, 父亲是爷爷的右子树
//注意 ,插入的结点一定是红色结点,能走到这里 ,说明不是空树,也不是只有根结点的树,所有 grandpa 必定存在
while(parent)
{
PNode grandpa = parent->pParent_;
PNode uncle;
if(parent == grandpa->pLeft_)
uncle = grandpa->pRight_;
else
uncle = grandpa->pLeft_;
//3 叔父 同红 --向上调整
if( parent->color_ == uncle->color_ )
{
parent->color_ = BLACK;
uncle->color_ = BLACK;
grandpa->color_= RED;
parent = grandpa;
}
//4 + 5 叔不存在或者 叔父不同色
else if(!uncle || parent->color != uncle->color_)
{
//1 确定形状 -- 左右型, 左左型 右左型,右右型 或者 直线型, √型
//2 选择旋转方案
if(parent == grandpa->pLeft_ && cur == parent->pRight_) // √型 将grandpa右旋
{
//先左旋,再右旋
rotateL(parent);//左旋形成执行行
rotateR(grandpa);
//调色
grandpa->color_ = RED;
parent->color_ = BLACK;
}
else if(parent == grandpa->Right_ && cur == parent->pLeft_)// √型 parent 左旋
{
rotateR(parent);//右旋形成直线型
rotareL(grandpa);
// 调色
grandpa ->color_ = RED;
cur->color_ = BLACK;
}
else if(parent == grandpa->pLeft_ && cur == parent ->pLeft_ )// 直线型 左直线
{
rotateR(grandpa);
parent->color_ = BLACK;
grandpa ->color_ = RED;
}
else //直线型 右直线
{
rotateL(grandpa);
parent->color = BLACK;
grandpa->color_ =RED;
}
}
}
}
pRoot->color_ = BLACK;
pHead_->pLeft_ = GetLeftMost();
pHead_->pRight_ = GetRightMost();
}
private:
PNode& GetRoot()
{
return pHead_->pParent_;
}
PNode& GetLeftMost()
{
PNode cur = pHead_->pParent_; //获取根节点
while(cur)
{
if(cur->pLeft_)
{
cur = cur->pLeft_;
}
else if(cur->pRight_)
{
cur = cur->pRight_;
}
else
return cur;
}
}
PNode& GetRightMost()
{
PNode cur = pHead_->pParent_;
while(cur)
{
if(cur->pRight_)
{
cur = cur->pRight_;
}
else if(cur->pLeft_)
{
cur = cur ->pLeft_;
}
else
return cur;
}
}
//检查是否为红黑树
bool IsRBTree()
{
PNode cur = GetRoot();
if(! cur) //空树
{
std::cout<<"满足"<<std::endl;
return true;
}
else if( cur ->color_ != BLACK )
{
std::cout<<"根节点颜色不满足 "<<std::endl;
return false;
}
else
{
// 获取 任意一条路径上黑色叶子个数
int black_count =0;
while(cur)
{
if(cur && cur->color_ == BLACK)
++black_count;
cur = cur->pLeft_;
}
int k = 0;
return IsEqualpath(black_count, k ,GetRoot());
}
}
bool IsEqualpath(int bk, int k, PNode pRoot)
{
// 终止条件
if(pRoot == nullptr)
{
if(k != bk)
{
std::cout<<"存在路径黑色节点不相等的情况"<<std::endl;
return false;
}
return true;
}
if( pRoot == BLACK )
{
++k;
}
// 不是黑色就是v红色结点,红色结点需要判断 是否存在相连通红
else
{
PNode parent = pRoot->pParent_;
if(parent && parent->color_ == RED)
{
std::cout<<"存在相邻同为红色的结点,因此不是红黑树"<<std::endl;
return false;
}
return IsEqualpath(bk,k, pRoot->pLeft_) &&
IsEqualpath(bk,k ,pRoot->pRight_);
}
}
private:
PNode pHead_;//红黑树根节点
};