网站的开发公司倒闭对网站使用seosem顾问
web/
2025/10/5 20:13:47/
文章来源:
网站的开发公司倒闭对网站使用,seosem顾问,网站怎么做别名,免费做网站刮刮卡红黑树 引言红黑树的介绍实现结点类insert搜索插入位置插入调整当parent为gparent的左子结点当parent为gparent的右子结点 参考源码测试红黑树是否合格总结 引言
在上一篇文章中我们认识了高度平衡的平衡二叉树AVL树#xff1a;戳我看AVL树详解哦 #xff08;关于旋转调整的… 红黑树 引言红黑树的介绍实现结点类insert搜索插入位置插入调整当parent为gparent的左子结点当parent为gparent的右子结点 参考源码测试红黑树是否合格总结 引言
在上一篇文章中我们认识了高度平衡的平衡二叉树AVL树戳我看AVL树详解哦 关于旋转调整的部分在AVL树的时候已经详细介绍过了如果大家对旋转调平衡的部分有疑惑的话请移步至AVL树的详解
由于AVL树的高度平衡其平均搜索时间复杂度几乎可以达到严格的O(logN)。同样也因为平衡的程度很高在维护平衡上时间的花费对于搜索上时间的提升是得不偿失的。 大多数情况下我们并不需要很高的平衡程度只需要达成一种接近平衡的状态搜索的平均时间复杂度基本达到O(logN)即可。 红黑树就是这样的一种结构它的最高子树的高度小于等于最低子树的二倍。通过减少调平衡时的时间成本来提高效率
红黑树的介绍
红黑树是平衡二叉树的一种他在满足二叉搜索树特性的基础上给每个结点增加了一个颜色属性包括Red与Black并要求从根节点到一个叶子结点形成的任意一条路径中通过对结点颜色的限制规则没有任何一条路径回比其他的路径长一倍 红黑圣诞树
对于红黑树结点颜色的限制规则如下
每个结点的颜色只有红色或黑色两种根结点的颜色一定是黑色的如果某一个结点的颜色是红色的它的两个孩子结点的颜色一定是黑色即不存在两个连续的红色结点对于任一结点到叶子结点的任一路径上包含的黑色结点的数量一定相等叶子结点一定是黑色的这里的叶子结点直最后的nullptr
当满足上面的所有规则时根节点到叶子结点的任一路径就都不可能比其他路径长一倍。由于不存在连续的红色结点所以当黑色结点的数量 n 一定时最长路径的长度为2 * n最短路径的长度为 n 所以不可能相差一倍以上。这样就达成了一种相对平衡的状态并不需要经常去旋转调平了。
实现
红黑树的实现在之前的二叉搜索树上增加了结点的颜色以及对于结点的颜色调整的部分 在本篇文章中依旧实现 K-V的模式 的树 并且以非递归实现insert 为防止命名冲突将实现放在我们的命名空间qqq中
结点的颜色我们使用 枚举常量enum Color 来表示
RBTree是一个类模板有两个模板参数即K与V表示其中存储的索引类型与值类型 成员变量类型为Node*由结点类RBTreeNode重命名表示根结点的指针 //基本的代码结构
namespace qqq
{enum Color //枚举常量表示颜色{RED,BLUCK};templateclass K, class Vstruct RBTreeNode //结点类{};templateclass K, class Vclass RBTree //红黑树{typedef RBTreeNodeK, V Node;public:bool insert(const pairK, V kv){}protected:Node* _root nullptr;};
}结点类
首先对红黑树的结点进行实现
RBTreeNode是也一个类模板 两个模板参数同样为K与V表示索引与值的类型 在结点中储存数据的结构为pair其中first为K类型second为V类型
成员变量包括结点中的数据 _kv 指向父亲结点的指针 _parent 指向左右孩子结点的指针 _left 与 _right 表示结点颜色的枚举常量 _col
templateclass K, class V
struct RBTreeNode //结点类
{pairK, V _kv;RBTreeNodeK, V* _parent;RBTreeNodeK, V* _left;RBTreeNodeK, V* _right;Color _col;
};结点类的构造函数 我们需要实现一个默认构造函数 参数类型为const pairK, V缺省值为一个pairK, V的匿名对象 对于父子结点的指针在初始化列表中初始化为nullptr即可 对于结点的颜色在初始化列表中初始化为RED要保证每条路上黑色结点的个数相等就必须初始化为红色 RBTreeNode(const pairK, V kv pairK, V()): _kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED){}insert
红黑树的insert分为两个部分即搜索并插入以及调整使其满足红黑树的性质
搜索插入位置
与二叉搜索树类似搜索并插入时首先用要插入的pair对象创建一个新结点newnode与此同时使用结点指针 cur来记录当前位置 parent来记录cur的父结点便于后面插入然后 while循环向下查找 插入的位置当newnode小于cur的元素时向左查找当newnode大于cur的元素时向右查找相等时即该元素已经存在返回false当cur为nullptr时表示找到了插入的位置循环终止
// 部分代码搜索插入位置 //
Node* newnode new Node(kv);
Node* parent nullptr;
Node* cur _root;while (cur ! nullptr) //搜索
{if (newnode-_kv.first cur-_kv.first){parent cur;cur parent-_right;}else if (newnode-_kv.first cur-_kv.first){parent cur;cur parent-_left;}else //相等即插入失败{return false;}
}插入
将newnode插入红黑树即将newnode与parent链接这时就需要判断parent是否为空当parent为空时即cur就是根节点_root即newnode是这棵红黑树中的第一个结点将 其赋值给_root 即可当parent不为空时 还需要判断cur位于parent的左边还是右边然后再插入 // 局部代码插入newnode //
if (parent nullptr) //插入
{_root newnode;
}
else if (newnode-_kv.first parent-_kv.first)
{parent-_left newnode;newnode-_parent parent;cur newnode;
}
else
{parent-_right newnode;newnode-_parent parent;cur newnode;
}调整
在完成插入后首先需要判断的是parent指针的状态
当parent为空时表明当前结点为根结点将其颜色改为BLACK即可调整完毕当parent指向结点的颜色为BLACK 时如果是在刚插入时新插入的结点颜色为RED不会影响该路径的黑色节点个数所以不再需要调整。如果是在向上调整的过程中parent指向的结点为BLACK也意味着整棵树调整结束了这一点在后面的调整部分会详细介绍除了上面不用调整的两种情况外其余的情况即 parent指向的结点存在且为RED 的情况就需要进行调整了。调整是自下而上的循环向上调整直到 parent为空或parent指向的结点为BLACK时循环结束调整完成
// 部分代码自下而上调整的循环框架 //
while (parent ! nullptr parent-_col ! BLUCK)
{}开始调整时首先对parent为gparentcur的祖父结点的左子结点还是右子结点做一分类讨论当parent不为黑时由于根结点必须为黑所以parent不是根结点所以gparent一定存在
当parent为gparent的左子结点
当parent为gparent的左子结点时我们首先要考察cur叔叔结点的情况 当cur的叔叔结点为RED 时不需要进行旋转调整只需要将gparent指向结点设置为RED将parent和cur的叔叔结点全部设置为BLACK即可 由于在调整完颜色后gparent指向结点颜色就为RED了这时如果gparent父结点的颜色正好为红就出现了连续的两个红色结点。所以需要将cur向上移动两个结点再对其parent指向的结点继续进行判断 当cur的叔叔结点为BLACK或不存在时就需要进行旋转调整了 旋转的逻辑与之前AVL树类似当cur位于parent的左边时即左左——单次右旋 当cur位于parent的右边时即左右——左右双旋 旋转调整后将gparent指向结点设置为RED将子树顶部的结点设置为BLACK 即可parent指向的结点或cur指向的结点
经过旋转后的红黑树子树顶部的颜色一定为黑色再向上也就不会存在两个连续的红色结点的问题了所以旋转之后直接终止循环即可。
需要注意的是叔叔是黑色结点的情况一定是出现在调整过程中发生的当叔叔结点为黑色时cur下的路径中一定存在着与gparent右子树中黑色结点相同个数的黑色结点parent下的路径中同样也存在着相同数目的黑色结点这样在旋转调平衡后这棵子树中的所有路径中的黑色结点数目与之前是不变的。
//部分代码当parent为gparent左子结点的情况
if (parent gparent-_left)
{if (gparent-_right nullptr || gparent-_right-_col BLUCK) //1.叔叔结点不存在或为黑需要旋转并调色{if (cur parent-_left)//左左-单次右旋{RotateR(gparent);parent-_col BLUCK;gparent-_col RED;}else//左右-左旋右旋{RotateL(parent);RotateR(gparent);cur-_col BLUCK;gparent-_col RED;}break; //通过旋转调整后该子树的根结点一定是黑所以可以直接结束循环}else if (gparent-_right-_col RED) //2.叔叔结点为红通过调色即可实现红黑树{//调色parent-_col BLUCK;gparent-_right-_col BLUCK;gparent-_col RED;//继续向上cur gparent;parent cur-_parent;}else{assert(0);}
}当parent为gparent的右子结点
当parent为gparent的右子结点时与上面的情况一致只是左右对调了所以这里只给出图示与代码如果在这种情况下遇到了问题希望你在上面的情况中能够找到答案 叔叔结点为RED仅调整颜色 叔叔结点为BLACK或不存在左单旋或右左双旋: 左单旋 右左双旋 旋转后调整颜色
旋转后子树顶部的结点一定为BLACK所以直接break即可。
//部分代码当parent为gparent右子结点的情况 //
else //parent gparent-_right
{if (gparent-_left nullptr || gparent-_left-_col BLUCK) //1.叔叔结点不存在或为黑需要旋转并调色{if (cur parent-_right)//右右-单次左旋{RotateL(gparent);parent-_col BLUCK;gparent-_col RED;}else//右左-右旋左旋{RotateR(parent);RotateL(gparent);cur-_col BLUCK;gparent-_col RED;}break; //通过旋转调整后该子树的根结点一定是黑所以可以直接结束循环}else if (gparent-_left-_col RED) //2.叔叔结点为红通过调色即可实现红黑树{//调色parent-_col BLUCK;gparent-_left-_col BLUCK;gparent-_col RED;//继续向上cur gparent;parent cur-_parent;}else{assert(0);}
}在while循环调整结束之后再将根结点_root的颜色改为BLACK统一做处理insert的整体代码在这里就不做展示了大家跳转至参考源码部分查看即可。
参考源码
namespace qqq
{enum Color //枚举常量表示颜色{RED,BLUCK};templateclass K, class Vstruct RBTreeNode //结点类{pairK, V _kv;RBTreeNodeK, V* _parent;RBTreeNodeK, V* _left;RBTreeNodeK, V* _right;Color _col;RBTreeNode(const pairK, V kv pairK, V()): _kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED){}};templateclass K, class Vclass RBTree //红黑树{typedef RBTreeNodeK, V Node;public:bool insert(const pairK, V kv){ //先插入Node* newnode new Node(kv);Node* parent nullptr;Node* cur _root;while (cur ! nullptr) //搜索{if (newnode-_kv.first cur-_kv.first){parent cur;cur parent-_right;}else if (newnode-_kv.first cur-_kv.first){parent cur;cur parent-_left;}else //相等即插入失败{return false;}}if (parent nullptr) //插入{_root newnode;}else if (newnode-_kv.first parent-_kv.first){parent-_left newnode;newnode-_parent parent;cur newnode;}else{parent-_right newnode;newnode-_parent parent;cur newnode;}//调整颜色以及旋转使满足红黑树while (parent ! nullptr parent-_col ! BLUCK) //当parent不为黑时由于根结点必须为黑所以parent不是根结点所以gparent一定存在{Node* gparent parent-_parent;if (parent gparent-_left) {if (gparent-_right nullptr || gparent-_right-_col BLUCK) //1.叔叔结点不存在或为黑需要旋转并调色{if (cur parent-_left)//左左-单次右旋{RotateR(gparent);parent-_col BLUCK;gparent-_col RED;}else//左右-左旋右旋{RotateL(parent);RotateR(gparent);cur-_col BLUCK;gparent-_col RED;}break; //通过旋转调整后该子树的根结点一定是黑所以可以直接结束循环}else if (gparent-_right-_col RED) //2.叔叔结点为红通过调色即可实现红黑树{//调色parent-_col BLUCK;gparent-_right-_col BLUCK;gparent-_col RED;//继续向上cur gparent;parent cur-_parent;}else{assert(0);}}else{if (gparent-_left nullptr || gparent-_left-_col BLUCK) //1.叔叔结点不存在或为黑需要旋转并调色{if (cur parent-_right)//右右-单次左旋{RotateL(gparent);parent-_col BLUCK;gparent-_col RED;}else//右左-右旋左旋{RotateR(parent);RotateL(gparent);cur-_col BLUCK;gparent-_col RED;}break; //通过旋转调整后该子树的根结点一定是黑所以可以直接结束循环}else if (gparent-_left-_col RED) //2.叔叔结点为红通过调色即可实现红黑树{//调色parent-_col BLUCK;gparent-_left-_col BLUCK;gparent-_col RED;//继续向上cur gparent;parent cur-_parent;}else{assert(0);}}}_root-_col BLUCK;return true;}void RotateL(Node* parent){Node* gparent parent-_parent;Node* cur parent-_right;Node* curleft cur-_left;parent-_right curleft;if (curleft ! nullptr){curleft-_parent parent;}cur-_left parent;parent-_parent cur;cur-_parent gparent;if (gparent nullptr){_root cur;}else{if (cur-_kv.first gparent-_kv.first){gparent-_left cur;}else{gparent-_right cur;}}}void RotateR(Node* parent){Node* gparent parent-_parent;Node* cur parent-_left;Node* curright cur-_right;parent-_left curright;if (curright ! nullptr){curright-_parent parent;}cur-_right parent;parent-_parent cur;cur-_parent gparent;if (gparent nullptr){_root cur;}else{if (cur-_kv.first gparent-_kv.first){gparent-_left cur;}else{gparent-_right cur;}}}bool isRBTree(){return isRBTree(_root);}protected:Node* _root nullptr;bool checkColor(Node* root, int countBluck, int baseBluck) //计算黑结点数量与红结点连续是否满足条件{if (root nullptr){if (baseBluck countBluck){return true;}return false;}if (root-_col RED root-_parent ! nullptr root-_parent-_col RED){return false;}if (root-_col BLUCK){countBluck;}return checkColor(root-_left, countBluck, baseBluck) checkColor(root-_right, countBluck, baseBluck);}bool isRBTree(Node* root){if (root nullptr)return true;if (root-_col RED)return false;//先计算一条路径中的黑色结点数量int baseBluck 0;Node* cur root;while (cur ! nullptr){if (cur-_col BLUCK){baseBluck;}cur cur-_right;}return checkColor(root, 0, baseBluck);}};
}测试红黑树是否合格
在写完红黑树的insert之后我们可以再编写一个测试模块来测试一棵树是否满足红黑树的特性
我们其实只需要判断两点即可
任一路径上的黑色结点个数是否相等是否存在连续的两个红色结点。
我们使用递归的方式来判断
判断任一路径上的黑色结点个数是否相等时必须要先计算某条路径上的黑色结点个数可以通过 while循环计算最右侧一条路径上的黑色结点个数。将cur从_root开始一直向右子节点遍历即可当cur为空时即该路径结束终止循环。
然后使用checkColor函数参数为当前结点指针Node*计算中的当前路径黑色结点个数int以及先前计算的最外层黑色结点个数int 递归计算每条路径上的黑色结点个数顺便判断是否存在连续的红色结点。 在递归过程中 当_root为空时即当前路径已经结束判断countBluck与baseBlack的值是否相等若相等返回true否则返回false 若当前结点为红色则判断其父结点是否为红色是就返回false 若当前结点为黑色countBluck加1并继续向左右子结点递归返回左子结点的结果右子结点的结果:
测试红黑树是否合格的代码这里就不赘述了大家可以在参考源码部分查找。这里来展示一下测试结果
namespace qqq
{void testfunc(){const int N 10000000;vectorint v;v.reserve(N);srand(time(0));for (size_t i 0; i N; i){v.push_back(i);}RBTreeint, int rbt;for (auto e : v){rbt.insert(make_pair(e, e));cout insert: e - rbt.isRBTree() endl;}cout rbt.isRBTree() endl;}
}int main()
{qqq::testfunc();return 0;
}因为这段测试代码中存在大量I/O所以运行速度很慢大家可以将cout注释掉只打印最后的结果。
总结
到此关于红黑树的知识就介绍完了 在接下来的文章中将会对红黑树进行封装即map与set尽情期待哦
如果大家认为我对某一部分没有介绍清楚或者某一部分出了问题欢迎大家在评论区提出
如果本文对你有帮助希望一键三连哦
希望与大家共同进步哦
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/87540.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!