算法导论之红黑树的学习
最近学习了二叉搜索树中的红黑树,感觉收获颇丰,在此写一篇文章小结一下学到的知识,顺便手写一下Java代码。
1.引言
2.红黑树的性质
- 每个节点是红色或黑色
- 根是黑色
- 叶节点(null)是黑色的
- 红色的节点的两个子结点均为黑色
- 对于每个节点,从该节点到其所有后代的简单路径上,均包含相同数目的黑色节点(我们把到叶节点的黑色节点数称为黑高)
- // 颜色枚举
- enum RBColor {
- RED, BLACK;
- }
- // 树节点类
- class RBTreeNode {
- RBTreeNode p = nullNode; // 父节点
- RBTreeNode left = nullNode; // 左子节点
- RBTreeNode right = nullNode; // 右子节点
- int val; // 值
- RBColor color; // 颜色
- public RBTreeNode() {};
- RBTreeNode(int val) {
- this.val = val;
- }
- @Override
- public String toString() {
- return " (" + val + " " + color + ") ";
- }
- // 用于表示空叶节点的静态变量
- public static RBTreeNode nullNode = new RBTreeNode() {
- {
- color = RBColor.BLACK; // 叶结点为黑色
- }
- @Override
- public String toString() {
- return " (null " + color + ") ";
- }
- };
- }
3.旋转
- /**
- * 左旋操作
- * @param root 根结点引用
- * @param node 旋转的节点
- * @return 根节点
- */
- public static RBTreeNode leftRotate(RBTreeNode root, RBTreeNode node) {
- if (node.right == RBTreeNode.nullNode)
- return root; // 左旋需要拥有右节点
- RBTreeNode right = node.right;
- // 旋转节点的右子树变为右节点的左子树
- node.right = right.left;
- if (node.right != RBTreeNode.nullNode)
- node.right.p = node;
- // 用右节点代替旋转节点位置
- if (node.p != RBTreeNode.nullNode) {
- right.p = node.p;
- if (node.p.left == node)
- node.p.left = right;
- else
- node.p.right = right;
- } else {
- root = right; // 没有父节点的节点为根结点
- root.p = RBTreeNode.nullNode;
- }
- // 右节点的左子树变为旋转节点
- right.left = node;
- node.p = right;
- return root;
- }
- /**
- * 右旋操作
- * @param root 根结点引用
- * @param node 旋转节点
- * @return 根节点
- */
- public static RBTreeNode rightRotate(RBTreeNode root, RBTreeNode node) {
- if (node.left == RBTreeNode.nullNode)
- return root; // 右旋需要有左节点
- RBTreeNode left = node.left;
- // 旋转节点的左子树变为左节点的右子树
- node.left = left.right;
- if (node.left != RBTreeNode.nullNode)
- node.left.p = node;
- // 用左节点代替旋转节点
- if (node.p != RBTreeNode.nullNode) {
- left.p = node.p;
- if (node.p.left == node)
- node.p.left = left;
- else
- node.p.right = left;
- } else {
- root = left;
- root.p = RBTreeNode.nullNode;
- }
- // 左节点的右子树变为旋转节点
- left.right = node;
- node.p = left;
- return root;
- }
4.插入
- /**
- * 红黑树插入操作
- * @param root 根结点引用
- * @param insertNode 要插入的新节点
- * @return 根节点
- */
- public static RBTreeNode rbInsert(RBTreeNode root, RBTreeNode insertNode) {
- RBTreeNode position = root, parent = RBTreeNode.nullNode; // position为插入位置,parent为该位置的父节点
- while (position != RBTreeNode.nullNode) {
- parent = position;
- if (insertNode.val < position.val) // 比该节点元素小的节点应该插入其左子树
- position = position.left;
- else // 比该节点元素大的节点应该插入其右子树
- position = position.right;
- }
- insertNode.p = parent;
- if (parent == RBTreeNode.nullNode) // 没有父节点的节点为根结点
- root = insertNode;
- else if (insertNode.val < parent.val) // 插入为左节点
- parent.left = insertNode;
- else // 插入为右节点
- parent.right = insertNode;
- insertNode.color = RBColor.RED; // 把新插入的节点染成红色
- return rbInsertFixup(root, insertNode); // 修复插入时红黑树性质
- }
关键词:叔节点
情况1:插入节点z的叔节点y为红色
情况2:插入节点z的叔节点y是黑色的,且z是一个右孩子
情况3:插入节点z的叔节点y是黑色的,且z是一个左孩子
- 交换父节点x和祖父节点w的颜色
- 对祖父节点w进行右旋
- /**
- * 修復插入時违反的红黑树性质
- * @param root 根节点引用
- * @param node 修复节点
- * @return 根节点
- */
- public static RBTreeNode rbInsertFixup(RBTreeNode root, RBTreeNode node) {
- // 修复节点不是根节点且为红色时
- RBTreeNode parent = node.p, grandParent, parentBorther;
- while(parent != RBTreeNode.nullNode && parent.color == RBColor.RED) {
- grandParent = parent.p;
- if (grandParent.left == parent) { // 父节点为左节点
- parentBorther = grandParent.right; // 叔节点为右节点
- if (parentBorther != RBTreeNode.nullNode && parentBorther.color == RBColor.RED) { // case 1
- grandParent.color = RBColor.RED; // 祖父节点改为红色
- parent.color = RBColor.BLACK; // 父节点和叔节点改为黑色
- parentBorther.color = RBColor.BLACK;
- node = grandParent; // 对祖父节点继续遍历
- } else {
- if (parent.right == node) { // case 2
- root = leftRotate(root, parent); // 对父节点左旋
- // 交换node和parent的引用
- RBTreeNode temp = node;
- node = parent;
- parent = temp;
- }
- // case 3
- grandParent.color = RBColor.RED; // 祖父染成红色
- parent.color = RBColor.BLACK; // 父节点染成黑色
- root = rightRotate(root, grandParent); // 对祖父右旋
- node = root; // 把节点置为根节点退出修复
- }
- } else { // 父节点为右节点,镜像处理
- parentBorther = grandParent.left;
- if (parentBorther != RBTreeNode.nullNode && parentBorther.color == RBColor.RED) { // case 1
- grandParent.color = RBColor.RED;
- parent.color = RBColor.BLACK;
- parentBorther.color = RBColor.BLACK;
- node = grandParent;
- } else {
- if (parent.left == node) { // case 2
- root = rightRotate(root, parent);
- RBTreeNode temp = node;
- node = parent;
- parent = temp;
- }
- // case 3
- grandParent.color = RBColor.RED;
- parent.color = RBColor.BLACK;
- root = leftRotate(root, grandParent);
- node = root;
- }
- }
- parent = node.p;
- }
- // 根节点染为黑色
- root.color = RBColor.BLACK;
- return root;
- }
5.删除
- 删除节点没有子节点:直接把删除节点的位置置空即可
- 删除节点有一个子节点:用该子节点顶替删除节点的位置
- 删除节点有两个子节点:这是比较复杂的情况,此时我们要从删除节点的两边子树中寻找一个节点来顶替其位置,我们可以找右子树的最小节点或左子树的最大节点,本文给出的代码为寻找右子树的最小节点。同时在代码中我们把删除节点的颜色赋给顶替节点,从而使实际删除颜色的节点为顶替节点。
- /**
- * 红黑树删除操作
- * @param root 根节点引用
- * @param deleteNode 要删除的节点
- * @return 根节点
- */
- public static RBTreeNode rbDelete(RBTreeNode root, RBTreeNode deleteNode) {
- RBTreeNode replaceNode, fixNode = RBTreeNode.nullNode; // 顶替删除节点的代替节点、需要修复颜色的节点位置
- RBTreeNode fixNodeParent = deleteNode.p;
- RBColor deleteColor = deleteNode.color; // 记录被删除节点的颜色
- if (deleteNode.left == RBTreeNode.nullNode && deleteNode.right == RBTreeNode.nullNode) // 删除节点没有任何子结点
- replaceNode = RBTreeNode.nullNode;
- else if (deleteNode.right == RBTreeNode.nullNode) { // 处理只有左子节点的情况
- replaceNode = deleteNode.left;
- fixNode = replaceNode;
- } else if (deleteNode.left == RBTreeNode.nullNode) { //处理只有右子节点的情况
- replaceNode = deleteNode.right;
- fixNode = replaceNode;
- } else { // 处理有两个子节点的情况
- replaceNode = deleteNode.right;
- while (replaceNode.left != RBTreeNode.nullNode) // 找到右子树的最小节点
- replaceNode = replaceNode.left;
- fixNode = replaceNode.right; // 修复节点位置变为原顶替节点位置
- if (replaceNode.p == deleteNode) { // 特殊情况,右子树没有左节点
- if (fixNode != RBTreeNode.nullNode) // 修复节点不为空
- fixNode.p = replaceNode;
- fixNodeParent = replaceNode;
- } else {
- replaceNode.p.left = fixNode; // 修复节点顶替该节点的位置
- if (fixNode != RBTreeNode.nullNode) // 修复节点不为空
- fixNode.p = replaceNode.p;
- fixNodeParent = replaceNode.p;
- replaceNode.right = deleteNode.right;
- }
- // 用删除节点的颜色代替顶替节点的颜色,使得被删除颜色的节点实际变为顶替节点
- deleteColor = replaceNode.color;
- replaceNode.color = deleteNode.color;
- replaceNode.left = deleteNode.left;
- }
- if (replaceNode != RBTreeNode.nullNode) // 存在顶替节点
- replaceNode.p = deleteNode.p;
- if (deleteNode.p == RBTreeNode.nullNode) // 删除节点的父节点为空,是根节点
- root = replaceNode;
- else { // 删除节点不是根节点
- if (deleteNode.p.left == deleteNode)
- deleteNode.p.left = replaceNode;
- else
- deleteNode.p.right = replaceNode;
- }
- if (deleteColor == RBColor.BLACK) // 如果删除的颜色是黑色则需要进行修复
- root = rbDeleteFixup(root, fixNode, fixNodeParent);
- return root;
- }
- 如果该节点原来为红色,那么我们被删除的黑色可以直接覆盖其颜色不影响任何红黑性质
- 如果该节点是黑色同时他也是根节点,那么我们可以简单的“消除”掉节点上面的一层黑色
- 如果该节点是黑色,但不是根节点,我们只能通过旋转和重新着色的方法转换修复的位置或退出循环
关键词:兄弟节点
情况1:A的兄弟节点为红色
情况2:A的兄弟节点为黑色,其子节点均为黑色(下图灰色代表未知颜色)
情况3:A的兄弟节点为黑色,兄弟节点的左子节点为红色,右子节点为黑色
情况4:A的兄弟节点为黑色,兄弟节点的右子节点为红色
- 把父节点B和兄弟节点的右子节点E染成黑色,兄弟节点C染成父节点颜色
- 对父节点B进行左旋
- /**
- * 修复删除时破坏的红黑树性质
- * @param root 根引用
- * @param fixNode 修复位置
- * @param parent 修复位置的父节点(修复位置为叶结点时使用)
- * @return 根
- */
- public static RBTreeNode rbDeleteFixup(RBTreeNode root, RBTreeNode fixNode, RBTreeNode parent) {
- RBTreeNode brother;
- while (root != fixNode && fixNode.color == RBColor.BLACK) {
- parent = fixNode.p == null ? parent : fixNode.p; // 处理fixNode为nullNode情况
- if (fixNode == parent.left) { // 顶替位置在父节点左边
- brother = parent.right;
- if (brother.color == RBColor.RED) { // case 1
- // 交换父节点和兄弟节点的颜色
- RBColor temp = brother.color;
- brother.color = parent.color;
- parent.color = temp;
- // 父节点进行左旋
- root = leftRotate(root, parent);
- } else if (brother == RBTreeNode.nullNode) { // case 2
- // 兄弟节点为空,即为黑色,只需继续遍历父节点即可
- fixNode = parent;
- } else if (brother.left.color == RBColor.BLACK &&
- brother.right.color == RBColor.BLACK) { // case 2
- brother.color = RBColor.RED;
- fixNode = parent; // 继续遍历父节点
- } else { // case 3 and case 4
- if (brother.left.color == RBColor.RED &&
- brother.right.color == RBColor.BLACK) { // case 3
- // 兄弟节点染成红色,左子节点染成黑色
- brother.color = RBColor.RED;
- brother.left.color = RBColor.BLACK;
- // 兄弟节点右旋
- root = rightRotate(root, brother);
- brother = brother.p;
- }
- // case 4
- // 变色
- brother.color = parent.color;
- parent.color = RBColor.BLACK;
- brother.right.color = RBColor.BLACK;
- // 父节点左旋
- root = leftRotate(root, parent);
- break;
- }
- } else {
- brother = parent.left;
- if (brother.color == RBColor.RED) { // case 1
- // 交换父节点和兄弟节点的颜色
- RBColor temp = brother.color;
- brother.color = parent.color;
- parent.color = temp;
- // 父节点进行右旋
- root = rightRotate(root, parent);
- } else if (brother == RBTreeNode.nullNode) { // case 2
- // 兄弟节点为空,即为黑色,只需继续遍历父节点即可
- fixNode = parent;
- } else if (brother.left.color == RBColor.BLACK &&
- brother.right.color == RBColor.BLACK) { // case 2
- brother.color = RBColor.RED;
- fixNode = parent; // 继续遍历父节点
- } else { // case 3 and case 4
- if (brother.right.color == RBColor.RED &&
- brother.left.color == RBColor.BLACK) { // case 3
- // 兄弟节点染成红色,左子节点染成黑色
- brother.color = RBColor.RED;
- brother.right.color = RBColor.BLACK;
- // 兄弟节点右旋
- root = leftRotate(root, brother);
- brother = brother.p;
- }
- // case 4
- // 变色
- brother.color = parent.color;
- parent.color = RBColor.BLACK;
- brother.left.color = RBColor.BLACK;
- // 父节点左旋
- root = rightRotate(root, parent);
- break;
- }
- }
- }
- fixNode.color = RBColor.BLACK;
- return root;
- };
6.打印与测试函数
- public static void main(String[] args) {
- int num[] = new int[]{5, 4, 1, 6, 3, 2};
- List<RBTreeNode> list = new ArrayList<>();
- RBTreeNode root = RBTreeNode.nullNode;
- // 插入测试
- for (int i = 0; i < num.length; i++) {
- list.add(new RBTreeNode(num[i]));
- root = rbInsert(root, list.get(i));
- printRBTree(root);
- System.out.println("");
- }
- // 删除测试
- for (int i = 0; i < num.length; i++) {
- root = rbDelete(root, list.get(0));
- list.remove(0);
- printRBTree(root);
- System.out.println("");
- }
- }
- /**
- * 打印一颗红黑树
- * @param root 根节点的引用
- */
- public static void printRBTree(RBTreeNode root) {
- if (root == RBTreeNode.nullNode) {
- System.out.println("这是一颗空树");
- return;
- }
- Queue<RBTreeNode> q = new LinkedList<>();
- boolean allNull = false; // 是否全为空节点
- q.add(root);
- while (!allNull) { // 该行不是全为叶结点
- allNull = true;
- Queue<RBTreeNode> rowQ = new LinkedList<>(); // 用于存储一行的所有节点
- RBTreeNode node;
- while (!q.isEmpty()) {
- node = q.poll();
- System.out.print(node);
- if (node != RBTreeNode.nullNode) { // 该节点不是叶结点
- if (node.left != RBTreeNode.nullNode) {
- rowQ.add(node.left);
- allNull = false;
- } else
- rowQ.add(RBTreeNode.nullNode);
- if (node.right != RBTreeNode.nullNode) {
- rowQ.add(node.right);
- allNull = false;
- } else
- rowQ.add(RBTreeNode.nullNode);
- } else { // 该节点为叶节点
- rowQ.add(RBTreeNode.nullNode);
- rowQ.add(RBTreeNode.nullNode);
- }
- }
- q = rowQ;
- System.out.println("");
- }
- }
总结,没写不知道,一写吓一跳,用Java来实现红黑树还是有挺多麻烦点的:
- 在Java中不知道如何修改根引用,所以最后都在函数上补了返回值
- 刚开始没考虑null叶节点其实是算黑色节点的情况,后来补充了一个静态变量作为叶节点
- 用静态变量当叶节点使得叶节点是共享的,不能修改叶节点的left,right,p指针,因此又再删除时添加了fixParent变量
- 删除时拥有两个子树,但右子树没有左节点的情况是个坑……
转载于:https://www.cnblogs.com/KingIceMou/p/6984138.html
算法导论之红黑树的学习相关推荐
- 《算法导论》红黑树详解(一):概念
在学习红黑树之前,读者应先掌握二叉查找树的相关知识.学习红黑树或者二叉查找树,推荐大家看<算法导论>.<算法导论>原书第3版 高清PDF 带详细书签目录下载 密码:acis & ...
- 算法导论 之 红黑树 - 添加[C语言]
作者:邹奇峰 邮箱:Qifeng.zou.job@hotmail.com 博客:http://blog.csdn.net/qifengzou 日期:2013.12.24 21:00 转载请注明来自&q ...
- java红黑树_JAVA学习-红黑树详解
1.定义 红黑树是特殊的二叉查找树,又名R-B树(RED-BLACK-TREE),由于红黑树是特殊的二叉查找树,即红黑树具有了二叉查找树的特性,而且红黑树还具有以下特性: 1.每个节点要么是黑色要么是 ...
- 【数据结构和算法05】 红-黑树(转发)
2019独角兽企业重金招聘Python工程师标准>>> [数据结构和算法05] 红-黑树(看完包懂~) 置顶 2016年04月13日 15:50:25 eson_15 阅读数:526 ...
- 算法-查找(红黑树)
查找 符号表 最主要的目的是将一个键和一个值联系起来.用例能够将一个键值对插入符号表并希望在之后能够从符号表的所有键值对中按照键直接找到对应的值,即以键值对为单元的数据结构. 无序链表顺序查找 性能: ...
- 算法手札二:红黑树的插入原理,原理与实现篇
红黑树的五大性质(性质四与性质五特别重要) 1. 节点必须是红色或者是黑色 2. 根节点是黑色的 3. 所有的叶子节点是黑色的. 4. 每个红色节点的两个子节点是黑色的,也就是不能存在父子两个节点全是 ...
- python 红黑树_python学习笔记|红黑树(性质与插入)
定义 一种含有红黑节点并能自平衡的二叉查找树(BST) 性质 1.每个节点有红/黑标记位 2.根节点是黑色(硬性规定) 3.每个叶子节点(NIL)都是黑色的虚节点(由此引出性质5) 叶子节点 colo ...
- 红黑树原理学习以234树来学习(1)插入操作
** 前言: 234树在大部分的程序语言中实现比较困难,所以等价的用红黑树来实现. 核心思想:红黑树的红色节点上移到父亲节点就变成了一颗234树. 一:红黑树与234树的等价关系如下: 一个234树可 ...
- LLRB——红黑树的现代实现
一.本文内容 以一种简明易懂的方式介绍红黑树背后的逻辑实现2-3-4树,以及红黑树的插入.删除操作,重点在2-3-4树与红黑树的对应关系上,并理清红黑树相关操作的来龙去脉.抛弃以往复杂的实现,而分析红 ...
最新文章
- 4000个“不会数学”的程序员出现大反转!居然能学AI,玩算法,搞逻辑!背后原因首次曝光...
- Android允许应用程序使用Http明文网络传输
- antd Datepicker组件报错 ——date.clone is not a function或者date1.isAfter is not a function
- 美团NLP中心算法实习生招聘
- kettle工具的设计原则
- 在腾讯云服务器上实现java web项目部署
- 实战Fibre Channel之六: 发起端和目的端常用命令
- Spring的核心思想,依赖注入
- 关于保留小数点后几位数字之我见
- python画图时常用的颜色——color=‘ ’
- 安卓系统修改开机LOGO
- SPSS实现单因素方差分析
- Multi-Object Trackers
- 程序媛的2013总结以及2014展望
- 一位37岁被裁技术高管给你提个醒:在职场,这件事越早做越好
- 中标麒麟5.0安装(内含安装包)-小白手把手史上最全教程!
- ModelAndView返回mav时,报404
- 从头开始做一个智能家居设备:MQTT协议及使用
- 咕泡p6java架构师五期涨薪班
- Python3.6+PyQT5+Pyserial 实现简单串口助手