C++_图解手撕红黑树的插入-查找-判断_KeyValue模型(三叉链)
Posted dodamce
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++_图解手撕红黑树的插入-查找-判断_KeyValue模型(三叉链)相关的知识,希望对你有一定的参考价值。
文章目录
- 1.红黑树概念
- 红黑树的性质
- 2.红黑树KV模型
- 3.红黑树的插入
- ①p是g左子树 cur为红,p为红,g为黑,u存在且为红
- ②p是g的左子树 cur为红,p为红,g为黑,u不存在/u为黑(cur,p,g为一条直线)_单旋
- ③p是g的左子树 cur为红,p为红,g为黑,u不存在/u为黑(cur,p,g为折线)_双旋
- ④p是g的右子树 cur为红,p为红,g为黑,u存在且为红
- ⑤p是g的右子树 cur为红,p为红,g为黑,u不存在/u为黑(cur,p,g是一条直线)_单旋
- ⑥p是g的右子树 cur为红,p为红,g为黑,u不存在/u为黑(cur,p,g为折线)_双旋
- 红黑树的插入代码
- 4.判断一棵树是否是红黑树
- 5.红黑树通过键值key查找节点
- 6.红黑树的打印
- 7.红黑树的析构函数
- 8.红黑树插入,查找,判断完整代码
- 9.测试红黑树
- 10.代码链接
1.红黑树概念
先看AVL树:
AVL树为高度平衡二叉搜索树
AVL树插入的实现
再看红黑树:
红黑树也是二叉搜索树。但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出2倍,因而是接近平衡的
红黑树的性质
- 每个结点不是红色就是黑色
- 根节点是黑色的
- 如果一个节点是红色的,则它的两个孩子结点是黑色的
- 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
- 每个空节点都是黑色的。
保证上述性质后,红黑树就确保没有一条路径会比其他路径长出2倍。
原因:假设一条路径上黑色节点为3个。这颗树最短路径为全黑色3,最长路径为黑红交替6。最长路径正好为最短路径的二倍,正好符合红黑树结构。
2.红黑树KV模型
基本结构
#pragma once
#include<iostream>
using namespace std;
enum Color
RED = 0, BLACK,
;
template<class Key, class Value>
struct RBTreeNode
RBTreeNode<Key, Value>* _left;
RBTreeNode<Key, Value>* _right;
RBTreeNode<Key, Value>* _parent;
pair<Key, Value> _kv;
Color col;
RBTreeNode(const pair<Key, Value>& val)
:_left(nullptr), _right(nullptr), _parent(nullptr), _kv(val), col(RED)//默认节点颜色
;
template<class Key,class Value>
class RBTree
typedef RBTreeNode<Key, Value> Node;
public:
RBTree() :_root(nullptr)
pair<Node*, bool>Insert(const pair<Key, Value>val);//红黑树的插入
private:
Node* _root;
;
3.红黑树的插入
红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:
- 按照二叉搜索的树规则插入新节点。
- 修改节点颜色,保持红黑树特性
情况一: 红黑树为空,此时插入时创建一个节点,并将节点的颜色改为黑色。
pair<Node*, bool>Insert(const pair<Key, Value>val)//红黑树的插入
if (_root == nullptr)
_root = new Node(val);
_root->col = BLACK;
return make_pair(_root, true);
情况二:
1.红黑树不为空,首先遍历红黑树,找到要插入的位置。同时找是否有重复的节点,如果发现有重复的节点插入失败,返回这个位置的指针和false。为了实现三叉链,还要一个parent指针指向cur的上一个节点
//二叉搜索树插入
Node* cur = _root;
Node* parent = nullptr;
while (cur != nullptr)
if (cur->_kv.first < val.first)
parent = cur;
cur = cur->_right;
else if (cur->_kv.first > val.first)
parent = cur;
cur = cur->_left;
else//键值重复,插入失败
return make_pair(cur, false);
2.上面这个函数结束后,此时cur就指向nullptr,parent指向要连接的上一个位置
cur创建一个新的红色节点,根据二叉搜索树特性,还要判断cur节点和parent节点值的大小,如果cur节点值小于parent插入到parent节点的左边,否则插入到parent节点的右边。
cur = new Node(val);
cur->col = RED;
if (parent->_kv.first > val.first)
parent->_left = cur;
else
parent->_right = cur;
cur->_parent = parent;
3.到这里,节点的插入就算完成了,我们还要调整红黑树的节点颜色,让其符合红黑树的规则。在这之前记录插入节点的位置,方便返回pair值
其次是调整红黑树颜色
为了方便描述,记录四个节点的名称cur,parent,uncle,grandparent节点,这四者位置入下图所示,简称c p u g
如果插入的parent是黑色,不需要调整树的颜色,插入完成。
插入的parent是红色,两个红色连续。需要处理
①p是g左子树 cur为红,p为红,g为黑,u存在且为红
根据上图可以写出代码
while (parent != nullptr && parent->col == RED)//停止条件看上图分析
Node* GradParent = parent->_parent;//记录gradparent节点
if (parent == GradParent->_left)//关键看Uncle节点的颜色
Node* Uncle = GradParent->_right;
//情况1:Uncle存在且为红
if (Uncle && Uncle->col == RED)
parent->col = Uncle->col = BLACK;
GradParent->col = RED;
cur = GradParent;//循环向上判断
parent = cur->_parent;
else//parent=GradParent->_right
......
_root->col = BLACK;//将根的颜色变为黑色,防止上面的过程将根节点变为红色
return make_pair(End, true);
②p是g的左子树 cur为红,p为红,g为黑,u不存在/u为黑(cur,p,g为一条直线)_单旋
首先:出现这种情况时,cur一定不是新插入的节点。一定是在情况一向上调整颜色时出现的
理由:
处理方式:
根据上面的分析,我们可以写出如下代码
pair<Node*, bool>Insert(const pair<Key, Value>val)//红黑树的插入
if (_root == nullptr)
_root = new Node(val);
_root->col = BLACK;
return make_pair(_root, true);
//二叉搜索树插入
Node* cur = _root;
Node* parent = nullptr;
while (cur != nullptr)
if (cur->_kv.first < val.first)
parent = cur;
cur = cur->_right;
else if (cur->_kv.first > val.first)
parent = cur;
cur = cur->_left;
else//键值重复,插入失败
return make_pair(cur, false);
cur = new Node(val);
cur->col = RED;
if (parent->_kv.first > val.first)
parent->_left = cur;
else
parent->_right = cur;
cur->_parent = parent;
Node* End = cur;//记录插入位置的节点
//控制路径
while (parent != nullptr && parent->col == RED)
Node* GradParent = parent->_parent;
if (parent == GradParent->_left)//关键看Uncle节点的颜色
Node* Uncle = GradParent->_right;
//情况1:Uncle存在且为红
if (Uncle && Uncle->col == RED)
parent->col = Uncle->col = BLACK;
GradParent->col = RED;
cur = GradParent;
parent = cur->_parent;
else//Uncle不存在或Uncle存在为黑色
if (cur == parent->_left)//右高,进行右旋
_Single_Right(GradParent);
GradParent->col = RED;
parent->col = BLACK;
break;//旋转后黑色节点个数不变,直接跳出循环
else//parent=GradParent->_right
......
_root->col = BLACK;//将根的颜色变为黑色,防止情况一最后到根上跳出根变成红色
return make_pair(End, true);
右单旋代码与AVL树的右旋转类似,根据下图旋转图写出代码即可
void _Single_Right(Node* parent)//右单旋根据图把对应关系连接起来
//记录要移动的节点
Node* SubL = parent->_left;
Node* SubLR = SubL->_right;
//连接
parent->_left = SubLR;
if (SubL->_right != nullptr)//修改父指针
SubLR->_parent = parent;
//连接
SubL->_right = parent;
Node* GradParent = parent->_parent;//记录这个节点的父节点,为了修改根节点
parent->_parent = SubL;//修改父指针
//调整根节点
if (parent == _root)//要旋转的节点为根节点
_root = SubL;
SubL->_parent = GradParent;
else//要旋转的节点是子树,修改GradParent指针
if (GradParent->_left == parent)
GradParent->_left = SubL;
else
GradParent->_right = SubL;
SubL->_parent = GradParent;
③p是g的左子树 cur为红,p为红,g为黑,u不存在/u为黑(cur,p,g为折线)_双旋
处理方式:
根据上图写出代码
pair<Node*, bool>Insert(const pair<Key, Value>val)//红黑树的插入
if (_root == nullptr)
_root = new Node(val);
_root->col = BLACK;
return make_pair(_root, true);
//二叉搜索树插入
Node* cur = _root;
Node* parent = nullptr;
while (cur != nullptr)
if (cur->_kv.first < val.first)
parent = cur;
cur = cur->_right;
else if (cur->_kv.first > val.first)
parent = cur;
cur = cur->_left;
else//键值重复,插入失败
return make_pair(cur, false);
cur = new Node(val);
cur->col = RED;
if (parent->_kv.first > val.first)
parent->_left = cur;
else
parent->_right = cur;
cur->_parent = parent;
Node* End = cur;//记录插入位置的节点
//控制路径
while (parent != nullptr && parent->col == RED)
Node* GradParent = parent->_parent;
if (parent == GradParent->_left)//关键看Uncle节点的颜色
Node* Uncle = GradParent->_right;
//情况1:Uncle存在且为红
if (Uncle && Uncle->col == RED)
parent->col = Uncle->col = BLACK;
GradParent->col = RED;
cur = GradParent;
parent = cur->_parent;
else//Uncle不存在或Uncle存在为黑色
if (cur == parent->_left)//右高,进行右旋
_Single_Right(GradParent);
GradParent->col = RED;
parent->col = BLACK;
else//折线形状,左右双旋
_Single_Left(parent);
_Single_Right(GradParent);
cur->col = BLACK;
parent->col = GradParent->col = RED;
break;//旋转后黑色节点个数不变,直接跳出循环
else//parent=GradParent->_right
......
_root->col = BLACK;//将根的颜色变为黑色,防止上面的过程将根节点变为红色
return make_pair(End, true);
左单旋与AVL树的左单旋类似,旋转原理与右单旋图片相同,不在赘述
void _Single_Left(Node* parent)//左旋转
//记录要移动的节点
Node* SubR = parent->_right;
Node* SubRL = SubR->_left;
//连接
parent->_right = SubRL;
if (SubRL != nullptr)
SubRL->_parent = parent;
SubR->_left = parent;
Node* GradParent = parent->_parent;//记录这个节点的父节点,为了修改根节点
parent->_parent = SubR;
//调整根节点
if (parent == _root)
_root = SubR;
SubR->_parent = GradParent;
else //要旋转的节点是子树,修改GradParent指针
if (GradParent->_left == parent)//旋转的是左子树,连接到左边
GradParent->_left = SubR;
else
GradParent->_right = SubR;//反之
SubR->_parent = GradParent;
④p是g的右子树 cur为红,p为红,g为黑,u存在且为红
这种情况与①相同,不在赘述
⑤p是g的右子树 cur为红,p为红,g为黑,u不存在/u为黑(cur,p,g是一条直线)_单旋
⑥p是g的右子树 cur为红,p为红,g为黑,u不存在/u为黑(cur,p,g为折线)_双旋
根据上图可以写出红黑树插入的下半逻辑
红黑树的插入代码
pair<Node*, bool>Insert(const pair<Key, Value>val)//红黑树的插入
if (_root == nullptr)
_root = new Node(val);
_root->col = BLACK;
return make_pair(_root, true);
//二叉搜索树插入
Node* cur = _root;
Node* parent = nullptr;
while (cur != nullptr)
if (cur->_kv.first < val.first)
parent = cur;
cur = cur->_right;
else if (cur->_kv.first > val.first)
parent = cur;
cur = cur->_left;
else//键值重复,插入失败
return make_pair(cur, false);
cur = new Node(val);
cur->col = RED;
if (parent->_kv.first > val.first)
parent->_left = cur;
else
parent->_right = cur;
cur->_parent = parent;
Node* End = cur;//记录插入位置的节点
//控制路径
while (parent != nullptr && parent->col == RED)
Node* GradParent = parent->_parent;
if (parent == GradParent->_left)//关键看Uncle节点的颜色
Node* Uncle = GradParent->_right;
//情况1:Uncle存在且为红
if (Uncle && Uncle->col == RED)
parent->col = Uncle->col = BLACK;
GradParent->col = RED;
cur = GradParent;
parent = cur->_parent;
else//Uncle不存在或Uncle存在为黑色
if (cur == parent->_left)//右高,进行右旋
_Single_Right(GradParent);
GradParent->col = RED;
parent->col = BLACK;
else//折线形状,左右双旋
_Single_Left(parent);
_Single_Right(GradParent);
cur->col = BLACK;
parent->col = GradParent->col = RED;
break;//旋转后黑色节点个数不变,直接跳出循环
else//parent=GradParent->_right
Node* Uncle = GradParent->_left;
if (Uncle != nullptr && Uncle->col == RED)
Uncle->col = parent->col = BLACK;
GradParent->col = RED;
cur = GradParent;
parent = cur->_parent;
手撕红黑树(Red-Black Tree)