C语言实现红黑树详细步骤+代码

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

C语⾔实现红⿊树详细步骤+代码
⽬录
红⿊树的概念
红⿊树的性质
红⿊树的定义与树结构
插⼊
新增结点插⼊后维护红⿊树性质的主逻辑
拆解讨论:
旋转
验证
红⿊树与AVl树的⽐较
红⿊树的应⽤
总结
红⿊树的概念
红⿊树,是⼀种⼆叉搜索树,但在每个结点上增加⼀个存储位表⽰结点的颜⾊,可以是Red或Black。

通过对任何⼀条从根到叶⼦的路径上各个结点着⾊⽅式的限制,红⿊树确保没有⼀条路径会⽐其他路径长出俩倍,因⽽是接近平衡的
概念总结:
红⿊树是⼆叉搜索树的升级,结点⾥⾯存放的成员col标记当前结点的颜⾊,它的最长路径最多是最短路径的⼆倍,红⿊树通过各个结点着⾊⽅式的限制接近平衡⼆叉树,但是不同于AVL的是AVL是⼀颗⾼度平衡的⼆叉树,红⿊树只是接近平衡
红⿊树的性质
每个结点不是红⾊就是⿊⾊根节点是⿊⾊的如果⼀个节点是红⾊的,则它的两个孩⼦结点是⿊⾊的对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数⽬的⿊⾊结点每个叶⼦结点都是⿊⾊的(此处的叶⼦结点指的是空结点)
红⿊树性质总结:
1、红⿊树结点的颜⾊只能是红⾊或者⿊⾊
2、红⿊树根节点必须是⿊⾊
3、红⿊树并没有连续的红⾊结点
4、红⿊树中从根到叶⼦的每⼀条路径都包含相同的⿊⾊结点
5、叶⼦是⿊⾊,表⽰空的位置
最长路径和最短路径概念:
最短路径:从根结点到叶⼦结点每⼀条路径的结点颜⾊都是⿊⾊的不包含红⾊
最长路径:红⿊交替,⿊⾊结点和红⾊结点的个数相等
思考:为什么满⾜上⾯的性质,红⿊树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?
假设结点个数为N,那么最短路径就是logN,最长路径就是2 * logN,所有并不存在最长路径超过最短路径2倍的情况
红⿊树的定义与树结构
//枚举红⿊颜⾊
enum colour
{
RED,
BLACK,
};
//定义红⿊树结点结构
template<class K,class V>
struct RBTreeNode
{
//构造
RBTreeNode(const pair<K, V>& kv = {0,0})
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
,_col(BLACK)
{ }
//定义三叉链
RBTreeNode<K, V>* _left; //左孩⼦
RBTreeNode<K, V>* _right;//右孩⼦
RBTreeNode<K, V>* _parent; //⽗亲
pair<K, V> _kv; //pair对象
//节点的颜⾊
colour _col; //定义枚举变量
};
//定义红⿊树
template<class K, class V>
class RBTree
{
typedef RBTreeNode<K, V> Node;
public:
//构造
RBTree()
:_root(nullptr)
{}
private:
Node* _root; //定义树的根节点
};
插⼊
插⼊过程类似搜索树的插⼊,重要的是维护红⿊树的性质pair<Node*, bool> Insert(const pair<K, V>& kv)
{
if (!_root) //空树处理
{
_root = new Node(kv);
_root->_col = BLACK;
return { _root, true };
}
//⼆叉搜索树的插⼊逻辑
Node* cur = _root, * parent = nullptr;
while (cur)
{
if (cur->_kv.first < kv.first)//插⼊结点⽐当前结点⼤
{
parent = cur;
cur = cur->_right;
}
else if (cur->_kv.first > kv.first) //插⼊结点⽐当前结点⼩
{
parent = cur;
cur = cur->_left;
}
else
{
return { cur, false }; //插⼊失败
}
}
cur = new Node(kv);
cur->_col = RED; //新增结点颜⾊默认设置为RED
//判断插⼊结点是否在parent的左边或者右边
if (parent->_kv.first > kv.first) //左边
{
parent->_left = cur;
cur->_parent = parent;
}
else //右边
{
parent->_right = cur;
cur->_parent = parent;
}
/* 红⿊树性质处理:
如果这棵树⼀开始是符合红⿊树的性质,但在新增结点之后,
导致失去了红⿊树的性质,这⾥需要控制结点的颜⾊和限制
每条路径上⿊⾊结点的个数,以上情况都要处理
*/
while (parent && parent->_col == RED) //⽗亲存在且⽗亲为红⾊
{
Node* grandfather = parent->_parent; //祖⽗
//⽗亲出现在祖⽗的左边需要考虑的情况
if(parent == grandfather ->left)
{
//1、uncle存在,uncle为红⾊
/*
如果parent和uncle都存在并且都为红⾊这是情况⼀,
需要将parent和uncle的颜⾊变成红⾊,祖⽗颜⾊变成⿊⾊
更新cur、parent、grandfather、uncle 继续向上调整
*/
//2、uncle不存在
/* 这⾥考虑两种旋转情况,直线单旋转,折线双旋
/*
cur出现在parent的左边 ,右单旋转
经过右单旋后,parent去做树的根,祖⽗做为右⼦树
//调节结点颜⾊
parent->_col = BLACK;
grandfather->_col = RED;
*/
/*
cur出现在parent的右边,左右双旋
经过双旋后,cur作为树的根,grandfather为右⼦树
调节结点颜⾊
cur->_col = BLACK;
grandfather->_col = RED;
*/
*/
}
else //⽗亲出现在祖⽗的右边
{
Node* uncle = grandfather->_left; //叔叔在左⼦树
/*
情况⼀:叔叔存在,且叔叔和⽗亲都是红⾊,那么就需要将⽗亲
和叔叔结点的颜⾊变成⿊⾊,再将祖⽗的颜⾊变成红⾊,
继续向上调整,更新孩⼦、⽗亲、祖⽗、叔叔的位置
*/
/*
情况⼆:叔叔不存在
/*
1、新增结点出现在⽗亲的右边,直线情况,左单旋处理
旋转完后parent去做⽗亲的根,grandfather做⽗亲
的左⼦树
//调节颜⾊,根为⿊,左右孩⼦为红
2、新增结点出现在⽗亲的左边,会出现折现的情况,
引发双旋,旋转完后,cur变成根,
parent和grandfaher去做cur的左右孩⼦
//调节颜⾊,根结点为⿊,左右孩⼦为红
*/
*/
}
}
//如果⽗亲不存在为了保证根结点是⿊⾊的,这⾥⼀定得将根结点处理为⿊⾊
_root->_col = BLACK;
}
新增结点插⼊后维护红⿊树性质的主逻辑
//1、⽗亲⼀定存在的情况,叔叔存在/不存在⽗亲叔叔结点颜⾊为红⾊
while (parent && parent->_col == RED) //⽗亲存在且⽗亲为红⾊
{
Node* grandfather = parent->_parent; //祖⽗
//如果⽗亲和叔叔结点颜⾊都是红⾊
if (parent == grandfather->_left)
{
Node* uncle = grandfather->_right;
if (uncle && uncle->_col == RED) //对应情况:uncle存在且为红
{
//处理:⽗亲和叔叔变成⿊⾊,祖⽗变成红⾊,继续向上调整
uncle->_col = parent->_col = BLACK;
grandfather->_col = RED;
//向上调整
cur = grandfather; //调整孩⼦
parent = cur->_parent;//调整⽗亲
}
else //uncle不存在,uncle存在且为⿊
{
//直线情况(cur在parent的左边):只考虑单旋,以grandfather为旋转点进⾏右单旋转,
//旋转完后将祖⽗的颜⾊变成红⾊,将⽗亲的颜⾊变成⿊⾊
if (parent->_left == cur)
{
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else //parent->_right == cur
{
//折线情况(cur在parent的右边):这⾥会引发双旋
RotateL(parent); //以parent为旋转点进⾏左单旋
RotateR(grandfather); //以grandfather为旋转点进⾏右单旋转
//旋转完后cur会去做树的根,那么设置为⿊⾊,
//为了保证每条路径的⿊⾊结点个数相同,grandfather结点颜⾊设置为红
cur->_col = BLACK;
grandfather->_col = RED; //⿊⾊结点个数相同
}
}
}
else //⽗亲在右⼦树
{
Node* uncle = grandfather->_left; //叔叔在左⼦树
if (uncle&& uncle->_col == RED) //情况⼀处理:叔叔存在,且叔叔的颜⾊是红⾊的(包含了⽗亲的颜⾊是红⾊的情况)
{
//根据情况⼀处理即可:叔叔和⽗亲变⿊,
//祖⽗变红(⽬的是为了每条路径的⿊⾊结点个数相同),继续向上
cur = grandfather; //孩⼦
parent = cur->_parent;//⽗亲
}
else //叔叔不存在
{
if (cur == parent->_right) //新增结点在⽗亲的右边,直线情况左单旋处理
{
//左单旋转,以grandfather为旋转点,旋转完后parent去做新的根,grandfather去做左⼦树
RotateL(grandfather);
//调节颜⾊
grandfather->_col = RED;
parent->_col = BLACK;
}
else //新增结点在⽗亲的左边,折线情况,引发双旋
{
//处理:以parenrt为旋转点做右单旋,再以grandfather为旋转点做左单旋
RotateR(parent); //右旋
RotateL(grandfather); //左旋
parent->_col = grandfather->_col = RED;
cur->_col = BLACK;
}
break;
}
}
_root->_col = BLACK;
}
拆解讨论:
以下只列举parent在grandfather左边的情况,⽽parent在grandfather右边的情况处理⽅式只是反过来的,读者可以⾃⾏画图,这⾥仅留参考代码Node* uncle = grandfather->_right;
if (uncle && uncle->_col == RED) //对应情况:uncle存在且为红
{
//处理:⽗亲和叔叔变成⿊⾊,祖⽗变成红⾊,继续向上调整
uncle->_col = parent->_col = BLACK;
grandfather->_col = RED;
//向上调整
cur = grandfather; //调整孩⼦
parent = cur->_parent;//调整⽗亲
}
else //uncle不存在,uncle存在且为⿊
{
//直线情况(cur在parent的左边):只考虑单旋,以grandfather为旋转点进⾏右单旋转, //旋转完后将祖⽗的颜⾊变成红⾊,将⽗亲的颜⾊变成⿊⾊
if (parent->_left == cur)
{
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else //parent->_right == cur
{
//双旋转
}
}
//折线情况(cur在parent的右边):这⾥会引发双旋
RotateL(parent); //以parent为旋转点进⾏左单旋
RotateR(grandfather); //以grandfather为旋转点进⾏右单旋转
//旋转完后cur会去做树的根,那么设置为⿊⾊,
//为了保证每条路径的⿊⾊结点个数相同,grandfather结点颜⾊设置为红
cur->_col = BLACK;
grandfather->_col = RED;
旋转
void RotateR(Node* parent) //右单旋
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR) subLR->_parent = parent; //防⽌subLR为nullptr
subL->_right = parent;
Node* parent_parent = parent->_parent; //指针备份
parent->_parent = subL;
if (_root == parent) //如果parent就是树的根
{
_root = subL; //subL取代parent
_root->_parent = nullptr;
}
else //如果parent并不是树的根
{
if (parent_parent->_left == parent) parent->_left = subL;
else parent_parent->_right = subL;
subL->_parent = parent_parent; //subL去做parent_parent的孩⼦ }
}
//左单旋
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL) subRL->_parent = parent;
subR->_left = parent;
Node* parent_parent = parent->_parent;
parent->_parent = subR;
if (_root == parent)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (parent_parent->_left == parent) parent_parent->_left = subR; else parent_parent->_right = subR;
subR->_parent = parent_parent;
}
}
验证
/*
红⿊树的⼏点性质在于:
1、根结点必须是红⾊的
2、不会出现连续的红⾊结点
3、所有路径的⿊⾊结点个数是相同的
*/
bool _CheckBlance(Node* root, int isBlackNum, int count)
{
if (!root)
{
if (isBlackNum != count)
{
printf("⿊⾊结点个数不均等\n");
return false;
}
return true; //遍历完整棵树,如果以上列举的⾮法情况都不存在就返回true
}
//检查是否出现连续的红⾊结点
if (root->_col == RED && root->_parent->_col == RED)
{
printf("出现了连续的红⾊结点\n");
return false;
}
//⾛前序遍历的过程中记录每⼀条路径⿊⾊结点的个数
if (root->_col == BLACK) count++;
//递归左右⼦树
return _CheckBlance(root->_left, isBlackNum, count) &&
_CheckBlance(root->_right, isBlackNum, count);
}
//验证红⿊树
bool CheckBlance()
{
if (!_root) return true; //树为null
//根结点是⿊⾊的
if (_root->_col != BLACK)
{
printf("根结点不是⿊⾊的\n");
return false;
}
//每⼀条路径⿊⾊结点的个数必须是相同的,
int isBlcakNum = 0;
Node* left = _root;
while (left)
{
if (left->_col == BLACK) isBlcakNum++; // 统计某⼀条路径的所以⿊⾊结点个数
left = left->_left;
}
//检查连续的红⾊结点,检查每⼀条路径的⿊⾊结点个数是否相等
return _CheckBlance(_root, isBlcakNum ,0);
}
红⿊树与AVl树的⽐较
红⿊树与AVL树的⽐较
红⿊树和AVL树都是⾼效的平衡⼆叉树,增删改查的
时间复杂度都是O( log n),红⿊树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对⽽⾔,降低了插⼊和旋转的次数,所以在经常进⾏增删的结构中性能⽐AVL树更优,⽽且红⿊树实现⽐较简单,所以实际运⽤中红⿊树更多。

红⿊树的应⽤
C++ STL库 – map/set、mutil_map/mutil_setJava 库linux内核其他⼀些库
完整代码博主已经放在git上了,读者可以参考
总结
到此这篇关于C语⾔实现红⿊树详细步骤+代码的⽂章就介绍到这了,更多相关C语⾔红⿊树内容请搜索以前的⽂章或继续浏览下⾯的相关⽂章希望⼤家以后多多⽀持!。

相关文档
最新文档