目录
一、概念
二、红黑树插入的实现
2.1红黑树节点的定义
2.2红黑树基础架构
2.3红黑树的插入
2.3.1按照二叉搜索树的规则插入新结点
2.3.2检测新插入节点,是否破坏红黑树性质来进行调整
2.3.2.1cur为红,p为红,g为黑,u存在且为红
2.3.2.2cur为红,p为红,g为黑,u不存在/u存在且为黑
2.3.2.2.1 解决方式①
2.3.2.2.2 解决方式②
2.4插入整体代码实现
2.5红黑树与AVL树的比较与应用
三、红黑树插入测试
前面已经学过了AVL树,如果说发明AVL树的人是个大佬,那么发明红黑树的人简直就是个天才,让我们一起来探索其奥秘。
一、概念
红黑树,同样是一种二叉搜索树,它是基于AVL树上,在每个节点增加了一个存储位表示节点的颜色,可以是Red或Black。通过对任何一条从根到叶子的路径上各个节点着色方式的限制,红黑树确保没有一条路径比其他路径长出两倍[1,2],因而是接近平衡的,满足该规则的同时其具有以下性质:
红黑树的性质:
1.顾名思义,每个节点不是红色就是黑色
2.根节点是黑色
3.如果一个节点是红色的,则它的两个孩子结点是黑色的(没有连续的红节点)
4.对于每个节点,从该节点到其所有后代叶节点(指的空节点(用NIL表示))的简单路径上,均包含相同数目的黑色节点(每条路径都包含相同数量的黑色节点)
5.每个叶子结点都是黑色的(叶子结点代表空节点,即将空节点代表为黑色节点)
思考:满足以上性质的红黑树,为什么就能够确保没有条路径比其他路径长出两倍[1,2]?
那我们根据性质来分析,有红节点那么其子节点必是黑节点,那么如何求最短路径?因为要确保每条路径黑色节点相同,那么最短路径就是这条路径从根节点开始没有红节点,全是黑色节点。
同样的如何求最长路径?那就是这条路径从根节点开始是一黑一红。
虽然路径的含义是从根节点到空节点,但是空节点默认当作为黑色节点,所以在计算路径长度时,就不包括空节点了。
我们用黑色节点代表B,用红色节点代表R,对于每条路径,B相等,因为没有连续的R,要么没有R,要么有R就必有B,所以R<=B,那么R+B <= B+B = 2 * B,所以最长路径不超过最短路径的两倍。
示意图:每条路径黑色节点相同
对于AVL树和红黑树,他们各有各自的优势,总体上没有太大的区别,但在维护平衡性方面要求更低,实现更简单,并且在实际应用中提供了更好的性能,所以选择了红黑树来作为map和set的底层,那么接下来,根据其规则一步一步分析来实现红黑树的插入,并采用KV模型。
二、红黑树插入的实现
2.1红黑树节点的定义
用一个枚举来存放一个节点的颜色,该节点中,包括左指针、右指针、指向父亲的指针,前面章节也学习了map和set的用法,这里了,对于节点的实现就采用kv模型,节点的值就用pair来存放。
enum Color//对于红黑节点,用枚举结构来表示
{
RED,
BLACK
};
template<class K,class V>
struct RBTreeNode
{
RBTreeNode(const pair<K,V> kv = pair<K,V>(), Color color = RED)
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_kv(kv)
,_color(color)
{}
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
pair<K, V> _kv;
Color _color;//默认给红色才符合规则,若默认给黑色的话,则插入的每个节点都是黑色节点,
//那么就不能保证每条路径的黑色节点相同,违反了第4条性质。而给红色,就可以根据规则调整。
};
2.2红黑树基础架构
template<class K, class V>
class RBTree
{
typedef RBTreeNode<K, V> Node;
typedef Node* PNode;
public:
RBTree()
:_Root(nullptr)
{}
private:
PNode _Root;
};
2.3红黑树的插入
对于红黑树的插入实现,可以分为两个大步骤:
1.按照二叉搜索树的规则插入新结点
2.检测新结点插入后,红黑树的性质是否被破坏来进行调整
2.3.1按照二叉搜索树的规则插入新结点
bool Insert(const pair<K,V>& kv)
{
if (_Root == nullptr)
{
_Root = new Node(kv,BLACK);
_Root->_parent = nullptr;
}
//寻找插入位置
PNode cur = _Root;
PNode parent = nullptr;
while (cur)
{
if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else
return false;
}
//插入
cur = new Node(kv);
if (kv.first < parent->_kv.first)
{
parent->_left = cur;
}
else if (kv.first > parent->_kv.first)
{
parent->_right = cur;
}
cur->_parent = parent;
//调整
//.......
}
2.3.2检测新插入节点,是否破坏红黑树性质来进行调整
因为新结点的默认颜色是红色,因此:
1.如果其双亲结点的颜色是黑色,没有违反红黑树任何性质,则不需要调整,若还有问题,则说明插入之前该树就有问题。如图:
2.但当新插入节点的父结点颜色为红色时,就违反了性质三不能有连续的红色节点,此时就需要进行调整,但是该调整还跟叔叔节点的情况有关,因此分了三种情况,我们一起来看。
约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点
2.3.2.1cur为红,p为红,g为黑,u存在且为红
2.3.2.2cur为红,p为红,g为黑,u不存在/u存在且为黑
对于此种情况下,其又分为两种小情况:
2.3.2.2.1 解决方式①
我们对当u不存在和u存在且为黑的情况依次进行分析:
当u不存在:
当u存在且为黑:
对于解决方式①的情况下,p为g的左孩子,cur为p的左孩子,不管u不存在还是u存在且为黑,符合情况一的先完成其对应的操作,符合情况二的,都是进行右单旋 + 变色(p变黑,g变红),调整完成后 p已经是黑色的了,也不需要继续往上调整了。若调整后还有问题,则说明插入节点之前,该树就已经不是平衡的红黑树了,而我们的操作都是在插入之前其都是平衡的情况下进行的,所以调整完成后,就没有必要继续往上调整了。
2.3.2.2.2 解决方式②
同理我们对当u不存在和u存在且为黑的情况依次进行分析:
当u不存在:
当u存在且为黑:
对于解决方式②的情况下,p为g的左孩子,cur为p的右孩子,不管u不存在还是u存在且为黑,符合情况一的先完成其对应的操作,符合情况二的,都是进行左右双旋 + 变色(cur变黑,g变红),调整完成后 cur已经是黑色的了,也不需要继续往上调整了。若调整后还有问题,则说明插入节点之前,该树就已经不是平衡的红黑树了,而我们的操作都是在插入之前其都是平衡的情况下进行的,所以调整完成后,就没有必要继续往上调整了。
对调整的情况分析完后,所以,我们对插入所有的分析转换成代码,如下:
2.4插入整体代码实现
bool Insert(const pair<K,V>& kv)
{
if (_Root == nullptr)
{
_Root = new Node(kv,BLACK);
_Root->_parent = nullptr;
}
//寻找插入位置
PNode cur = _Root;
PNode parent = nullptr;
while (cur)
{
if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else
return false;
}
//插入
cur = new Node(kv);
if (kv.first < parent->_kv.first)
{
parent->_left = cur;
}
else if (kv.first > parent->_kv.first)
{
parent->_right = cur;
}
cur->_parent = parent;
//调整
while (parent && parent->_color == RED)//只要停留在情况一就继续判断
{
PNode grandparent = parent->_parent;
PNode uncle = nullptr;
//先定好uncle的位置,不管uncle是否存在
if (parent == grandparent->_left)
{
uncle = grandparent->_right;
}
else
{
uncle = grandparent->_left;
}
if (uncle && uncle->_color == RED)//p为红、u存在且为红
{
// g
// p u
// cur
parent->_color = BLACK;
uncle->_color = BLACK;
grandparent->_color = RED;
//根节点,更新结束
if (grandparent == _Root)
{
grandparent->_color = BLACK;
break;
}
//往上更新
cur = grandparent;
parent = cur->_parent;
}
else if (cur == parent->_left && parent == grandparent->_left )//cur为p的左孩子,p为g的左孩子,p为红
{
// g
// p u
//cur
RotateR(grandparent);
parent->_color = BLACK;
grandparent->_color = RED;
break;
}
else if (cur == parent->_right && parent == grandparent->_right )//cur为p的右孩子,p为g的右孩子,p为红
{
// g
// u p
// cur
RotateL(grandparent);
parent->_color = BLACK;
grandparent->_color = RED;
break;
}
else if (cur == parent->_right && parent == grandparent->_left )//p为g的左孩子,cur为p的右孩子,p为红
{
// g
//p u
// cur
RotateL(parent);
RotateR(grandparent);
cur->_color = BLACK;
grandparent->_color = RED;
break;
}
else if (cur == parent->_left && parent == grandparent->_right)//p为g的右孩子,cur为p的左孩子,p为红
{
// g
//u p
// cur
RotateR(parent);
RotateL(grandparent);
cur->_color = BLACK;
grandparent->_color = RED;
break;
}
else
{
assert(false);
}
}
return true;
}
void RotateL(PNode parent)
{
PNode subR = parent->_right;
PNode subRL = subR->_left;
PNode pparent = parent->_parent;
if (parent == _Root)//更新根节点
{
_Root = subR;
subR->_parent = nullptr;
}
else
{
//更新parent的父节点指向
if (parent == pparent->_left)
{
pparent->_left = subR;
}
else
{
pparent->_right = subR;
}
subR->_parent = pparent;
}
//parent的右指针指向subRL,subRL的父节点指向parent
parent->_right = subR->_left;
if (subRL)//subR的左节点可能不存在
subRL->_parent = parent;
//subR的左指针指向parent,parent的父节点指向subR
subR->_left = parent;
parent->_parent = subR;
}
//右单旋
void RotateR(PNode parent)
{
PNode subL = parent->_left;
PNode subLR = subL->_right;
PNode pparent = parent->_parent;
if (_Root == parent)
{
_Root = subL;
subL->_parent = nullptr;
}
else
{
//更新parent的父节点指向
if (pparent->_left == parent)
{
pparent->_left = subL;
}
else
{
pparent->_right = subL;
}
subL->_parent = pparent;
}
//parent的左指针指向subLR,subLR的父节点指向parent
parent->_left = subLR;
if (subLR)//subR的右节点可能不存在
subLR->_parent = parent;
//subL的右指针指向parent,parent的父节点指向subL
subL->_right = parent;
parent->_parent = subL;
}
2.5红黑树与AVL树的比较与应用
同样的,对于红黑树的删除等操作,不在进行探究,因为有了插入的了解,就已经对红黑树的性质有了一定的掌握,如果对其他操作有额外兴趣的,可参照:《算法导论》或者《STL源码剖析》。
对比前一篇章AVL树的插入实现的理解,红黑树和AVL树都是高效的平衡二叉树,对于节点的操作的时间复杂度都是 log N,红黑树不追求绝对的平衡,其只需要保证最长路径不超过最短路径的2倍,且控制节点的颜色来控制相对平衡,只有在特定情况下,需要进行旋转,相对AVL树而言,降低了插入和旋转次数,当有了AVL树的基础,那么实现红黑树就会变得相对简单,所以在实际应用中,也会更多的采用红黑树
例如:
1.c++STL库 -- map/set、multimap/multiset
2.java库
3.Linux库等
三、红黑树插入测试
//RBTree.h
#pragma once
#include <iostream>
#include <assert.h>
using namespace std;
namespace bit
{
enum Color//对于红黑节点,用枚举结构来表示
{
RED,
BLACK
};
template<class K,class V>
struct RBTreeNode
{
RBTreeNode(const pair<K,V> kv = pair<K,V>(), Color color = RED)
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_kv(kv)
,_color(color)
{}
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
pair<K, V> _kv;
Color _color;//默认给红色才符合规则,若默认给黑色的话,则插入的每个节点都是黑色节点,
//那么就不能保证每条路径的黑色节点相同,违反了第4条性质。而给红色,就可以根据规则调整。
};
template<class K, class V>
class RBTree
{
typedef RBTreeNode<K, V> Node;
typedef Node* PNode;
public:
RBTree()
:_Root(nullptr)
{}
bool Insert(const pair<K,V>& kv)
{
if (_Root == nullptr)
{
_Root = new Node(kv,BLACK);
_Root->_parent = nullptr;
}
//寻找插入位置
PNode cur = _Root;
PNode parent = nullptr;
while (cur)
{
if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else
return false;
}
//插入
cur = new Node(kv);
if (kv.first < parent->_kv.first)
{
parent->_left = cur;
}
else if (kv.first > parent->_kv.first)
{
parent->_right = cur;
}
cur->_parent = parent;
//调整
while (parent && parent->_color == RED)//只要停留在情况一就继续判断
{
PNode grandparent = parent->_parent;
PNode uncle = nullptr;
//先定好uncle的位置,不管uncle是否存在
if (parent == grandparent->_left)
{
uncle = grandparent->_right;
}
else
{
uncle = grandparent->_left;
}
if (uncle && uncle->_color == RED)//p为红、u存在且为红
{
// g
// p u
// cur
parent->_color = BLACK;
uncle->_color = BLACK;
grandparent->_color = RED;
//根节点,更新结束
if (grandparent == _Root)
{
grandparent->_color = BLACK;
break;
}
//往上更新
cur = grandparent;
parent = cur->_parent;
}
else if (cur == parent->_left && parent == grandparent->_left )//cur为p的左孩子,p为g的左孩子,p为红
{
// g
// p u
//cur
RotateR(grandparent);
parent->_color = BLACK;
grandparent->_color = RED;
break;
}
else if (cur == parent->_right && parent == grandparent->_right )//cur为p的右孩子,p为g的右孩子,p为红
{
// g
// u p
// cur
RotateL(grandparent);
parent->_color = BLACK;
grandparent->_color = RED;
break;
}
else if (cur == parent->_right && parent == grandparent->_left )//p为g的左孩子,cur为p的右孩子,p为红
{
// g
//p u
// cur
RotateL(parent);
RotateR(grandparent);
cur->_color = BLACK;
grandparent->_color = RED;
break;
}
else if (cur == parent->_left && parent == grandparent->_right)//p为g的右孩子,cur为p的左孩子,p为红
{
// g
//u p
// cur
RotateR(parent);
RotateL(grandparent);
cur->_color = BLACK;
grandparent->_color = RED;
break;
}
else
{
assert(false);
}
}
return true;
}
void RotateL(PNode parent)
{
PNode subR = parent->_right;
PNode subRL = subR->_left;
PNode pparent = parent->_parent;
if (parent == _Root)//更新根节点
{
_Root = subR;
subR->_parent = nullptr;
}
else
{
//更新parent的父节点指向
if (parent == pparent->_left)
{
pparent->_left = subR;
}
else
{
pparent->_right = subR;
}
subR->_parent = pparent;
}
//parent的右指针指向subRL,subRL的父节点指向parent
parent->_right = subR->_left;
if (subRL)//subR的左节点可能不存在
subRL->_parent = parent;
//subR的左指针指向parent,parent的父节点指向subR
subR->_left = parent;
parent->_parent = subR;
}
//右单旋
void RotateR(PNode parent)
{
PNode subL = parent->_left;
PNode subLR = subL->_right;
PNode pparent = parent->_parent;
if (_Root == parent)
{
_Root = subL;
subL->_parent = nullptr;
}
else
{
//更新parent的父节点指向
if (pparent->_left == parent)
{
pparent->_left = subL;
}
else
{
pparent->_right = subL;
}
subL->_parent = pparent;
}
//parent的左指针指向subLR,subLR的父节点指向parent
parent->_left = subLR;
if (subLR)//subR的右节点可能不存在
subLR->_parent = parent;
//subL的右指针指向parent,parent的父节点指向subL
subL->_right = parent;
parent->_parent = subL;
}
void InOrder()
{
_InOrder(_Root);
}
void _InOrder(PNode Root)
{
if (Root == nullptr)
return;
_InOrder(Root->_left);
cout << Root->_kv.first << ":" << Root->_kv.second << endl;
_InOrder(Root->_right);
}
bool IsBalance()
{
if (_Root->_color != BLACK)
return false;
int blackcount = 0;
PNode cur = _Root;
while (cur)
{
if (BLACK == cur->_color)
blackcount++;
cur = cur->_left;
}
int k = 0;
return _IsBalance(_Root,k,blackcount);
}
bool _IsBalance(PNode Root, int k, int blackcount)
{
if (nullptr == Root)
{
if (k != blackcount)
{
cout << "违反性质四:每条路径黑色节点的个数必须相同" << endl;
return false;
}
return true;
}
if (BLACK == Root->_color)
k++;
if (RED == Root->_color && RED == Root->_parent->_color)
{
cout << "违反性质三:不能出现连续的红色节点" << endl;
return false;
}
return _IsBalance(Root->_left, k, blackcount) &&
_IsBalance(Root->_right, k, blackcount);
}
private:
PNode _Root;
};
void TestRBTree()
{
int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
//int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
RBTree<int, int> t;
for (auto e : a)
{
t.Insert(make_pair(e, e));
}
t.InOrder();
cout << t.IsBalance() << endl;
}
}
//test.cpp
#include "RBTree.h"
int main()
{
bit::TestRBTree();
return 0;
}
输出结果:
分析、代码是手敲的,若哪里有bug,或者哪里没懂的,请大家指正,谢谢~
下一篇章,将用红黑树来实现封装map/set