住上铺的师兄面试去了TX,就因为他和面试官光红黑树就聊了半个小时,建议收藏
上篇文章详细的给大家介绍了2-3-4树,本文就详细的介绍下红黑树的内容
红黑树
红黑树,Red-Black Tree 「RBT」是一个自平衡(不是绝对的平衡)的二叉查找树(BST),树上的每个节点都遵循下面的规则:
- 每个节点要么是黑色,要么是红色。
- 根节点是黑色。
- 每个叶子节点(NIL)是黑色。
- 每个红色结点的两个子结点一定都是黑色。
- 任意一结点到每个叶子结点的路径都包含数量相同的黑结点。
红黑树能自平衡,它靠的是什么?三种操作:左旋、右旋和变色
操作 | 描述 |
---|---|
左旋 | 以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点, 右子结点的左子结点变为旋 24000 转结点的右子结点,左子结点保持不变。 |
右旋 | 以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点, 左子结点的右子结点变为旋转结点的左子结点,右子结点保持不变。 |
变色 | 结点的颜色由红变黑或由黑变红。 |
1 旋转操作
1.1 概念讲解
左旋:以某个节点作为旋转点,其右子节点变为旋转节点的父节点,右子节点的左子节点变为旋转节点的右子节点,左子节点保持不变。
右旋:以某!个节点作为旋转点,其左子节点变为旋转节点的父节点,左子节点的右子节点变为旋转节点的左子节点,右子节点保持不变。
1.2 代码实现
先进行类结构定义
package com.bobo.util.treemap; public class BRTree { private static final boolean RED = false; private static final boolean BLACK = true; private RBNode root; public RBNode getRoot() { return root; } public void setRoot(RBNode root) { this.root = root; } /** * 表示 节点 * @param <K> * @param <V> */ static class RBNode<K extends Comparable<K>,V>{ // 节点是双向的 private RBNode parent; private RBNode left; private RBNode right; private boolean color; private K key; private V value; public RBNode() { } public RBNode(RBNode parent, RBNode left, RBNode right, boolean color, K key, V value) { this.parent = parent; this.left = left; this.right = right; this.color = color; this.key = key; this.value = value; } public RBNode getParent() { return parent; } public void setParent(RBNode parent) { this.parent = parent; } public RBNode getLeft() { return left; } public void setLeft(RBNode left) { this.left = left; } public RBNode getRight() { return right; } public void setRight(RBNode right) { this.right = right; } public boolean isColor() { return color; } public void setColor(boolean color) { this.color = color; } public K getKey() { return key; } public void setKey(K key) { this.key = key; } public V getValue() { return value; } public void setValue(V value) { this.value = value; } } }
左旋代码实现
/** * 围绕p左旋 * p pr(r) * / | / \ * pl pr(r) => p rr * / \ / \ * rl rr pl rl * * 左旋的时候 * p-pl 和 pr-rr的关系不变 * pr-rl 要变为 p-rl * 也就是 rl要变为 p的右子节点 * 同时 p要成为 rl 的父节点 * 还有就是要判断 p 是否有父节点 * 如果没有 * r 变为 root 节点 * 如果有 * r.parent = p.parent * 还要设置 r为 p.parent 的子节点(可能左也可能右) * 如果 p.parent.left == p * p.parent.left = r; * 否则 * p.parent.right = r; * 最后 * p.parent = r; * r.left = p; * @param p */ private void leftRotate(RBNode p){ if(p != null){ RBNode r = p.right; // 1.设置 pr-rl 要变为 p-rl // 把rl设置到p的右子节点 p.right = r.left; if(r.left != null){ // 设置rl的父节点为p r.left.parent = p; } // 2.判断p的父节点情况 r.parent = p.parent; // 不管 p是否有父节点,都把这个父节点设置为 r的父节点 if(p.parent == null){ root = r; // p没有父节点 则r为root节点 }else if(p.parent.left == p){ p.parent.left = r; // 如果p为 p.parent的左子节点 则 r 也为 p.parent的左子节点 }else{ p.parent.right = r; // 反之设置 r 为 p.parent的右子节点 } // 最后 设置 p 为 r 的左子节点 r.left = p; p.parent = r; } }
右旋实现:
/** * 围绕p右旋 * @param p */ public void rightRotate(RBNode p){ if(p != null){ RBNode r = p.left; p.left = r.right; if(r.right != null){ r.right.parent = p; } r.parent = p.parent; if(p.parent == null){ root = r; }else if(p.parent.left == p){ p.parent.left = r; }else{ p.parent.right = r; } r.right = p; p.parent = r; } }
2 新增节点
https://www.processon.com/view/link/60c21e25e401fd34a1514d25
2-3-4树中结点添加需要遵守以下规则:
- 插入都是向最下面一层插入
- 升元:将插入结点由 2-结点升级成 3-结点,或由 3-结点升级成 4-结点;
- 向 4-结点插入元素后,需要将中间元素提到父结点升元,原结点变成两个 2-结点,再把元素插入2-结点中,如果父结点也是 4-结点,则递归向上层升元,至到根结点后将树高加1;
而将这些规则对应到红黑树里,就是:
- 新插入的结点颜色为 红色 ,这样才可能不会对红黑树的高度产生影响。
- 2-结点对应红黑树中的单个黑色结点,插入时直接成功(对应 2-结点升元)。
- 3-结点对应红黑树中的 黑+红 子树,插入后将其修复成 红+黑+红 子树(对应 3-结点升元);
- 4-结点对应红黑树中的 红+黑+红 子树,插入后将其修复成 红色祖父+黑色父叔+红色孩子 子树,然后再把祖父结点当成新插入的红色结点递归向上层修复,直至修复成功或遇到 root 结点;
公式:红黑树+新增一个节点(红色)**=**对等的2-3-4树+新增一个节点
2.1 新增节点示例
我们通过新增2-3-4树的过程来映射对应的红黑树的节点新增
2-3-4树的新增(全部在叶子节点完成)
1.新增一个节点,2 节点
2.新增一个节点,与2节点合并,直接合并
3.新增一个节点,与3节点合并,直接合并
插入的值的位置会有3种情况
对应的红黑树为:
4.新增一个节点,与4节点合并,此时需要分裂
插入值的位置可能是
对应的红黑树的结构为
2.2 新增代码实现
红黑树的新增规则我们理清楚了,接下来就可以通过Java代码来具体的实现了。
先实现插入节点,这就是一个普通的二叉树的插入
/** * 新增节点 * @param key * @param value */ public void put(K key , V value){ RBNode t = this.root; if(t == null){ // 说明之前没有元素,现在插入的元素是第一个 root = new RBNode<>(key , value == null ? key : value,null); return ; } int cmp ; // 寻找插入位置 // 定义一个双亲指针 RBNode parent; if(key == null){ throw new NullPointerException(); } // 沿着跟节点找插入位置 do{ parent = t; cmp = key.compareTo((K)t.key); if(cmp < 0){ // 左侧找 t = t.left; }else if(cmp > 0){ // 右侧找 t = t.right; }else{ // 插入节点的值==比较的节点。值替换 t.setValue(value==null?key:value); return; } }while (t != null); // 找到了插入的位置 parent指向 t 的父节点 t为null // 创建要插入的节点 RBNode<K, Object> e = new RBNode<>(key, value == null ? key : value, parent); // 然后判断要插入的位置 是 parent的 左侧还是右侧 if(cmp < 0){ parent.left = e; }else{ parent.right = e; } // 调整 变色 旋转 fixAfterPut(e); }
然后再根据红黑树的特点来实现调整(旋转,变色)
private boolean colorOf(RBNode node){ return node == null ? BLACK:node.color; } private RBNode parentOf(RBNode node){ return node != null ? node.parent:null; } private RBNode leftOf(RBNode node){ return node != null ? node.left:null; } private RBNode rightOf(RBNode node){ return node != null ? node.right:null; } private void setColor(RBNode node ,boolean color){ if(node != null){ node.setColor(color); } } /** * 插入节点后的调整处理 * 1. 2-3-4树 新增元素 2节点添加一个元素将变为3节点 直接合并,节点中有两个元素 * 红黑树:新增一个红色节点,这个红色节点会添加在黑色节点下(2节点) --- 这种情况不需要调整 * 2. 2-3-4树 新增元素 3节点添加一个元素变为4节点合并 节点中有3个元素 * 这里有6中情况,( 根左左 根左右 根右右 根右左)这四种要调整 (左中右的两种)不需要调整 * 红黑树:新增红色节点 会添加到 上黑下红的节点中 = 排序后中间节点是黑色,两边节点是红色 * * 3. 2-3-4树:新增一个元素 4节点添加一个元素需要裂变:中间元素升级为父节点,新增元素与剩下的其中一个合并 * 红黑树:新增节点是红色+爷爷节点是黑色,父亲节点和叔叔节点为红色 调整为 * 爷爷节点变红色,父亲和叔叔节点变为黑色,如果爷爷节点为root节点则调整为黑色 * @param x */ private void fixAfterPut(RBNode<K, Object> x) { x.color = RED; // 本质上就是父节点是黑色的就不需要调整,对应的 2 3的情况 while(x != null && x != root && x.parent.color == RED){ // 1. x 的父节点是爷爷的 左孩子 if(parentOf(x) == parentOf(parentOf(x)).left){ // 获取当前节点的叔叔节点 RBNode y = rightOf(parentOf(parentOf(x))); // 情况3 if(colorOf(y) == RED){ // 说明是 上3的情况 变色处理 // 父亲节点和叔叔节点设置为黑色 setColor(parentOf(x),BLACK); setColor(y,BLACK); // 爷爷节点设置为 红色 setColor(parentOf(parentOf(x)),RED); // 递归处理 x = parentOf(parentOf(x)); }else{ // 情况 2 if(x == parentOf(x).right){ // 如果x是父节点的右节点那么我们需要先根据 父节点 左旋 x = parentOf(x); leftRotate(x); } // 叔叔节点为空 对应于 上面的情况2 // 将父节点变为黑色 setColor(parentOf(x),BLACK); // 将爷爷节点变为红色 setColor(parentOf(parentOf(x)),RED); // 右旋转 根据爷爷节点右旋转 rightRotate(parentOf(parentOf(x))); } }else{ // x 的父节点是爷爷是右孩子 // 获取父亲的叔叔节点 RBNode y = leftOf(parentOf(parentOf(x))); if(colorOf(y) == RED){ // 情况3 setColor(parentOf(x),BLACK); setColor(y,BLACK); setColor(parentOf(parentOf(x)),RED); x = parentOf(parentOf(x)); }else{ // 情况2 if( x == parentOf(x).left){ x = parentOf(x); rightRotate(x); } setColor(parentOf(x),BLACK); setColor(parentOf(parentOf(x)),RED); leftRotate(parentOf(parentOf(x))); } } } root.color = BLACK; }
2.3 插入节点
不通过2-3-4树来实现添加节点的分析,看大家是否能理解哦
插入的场景
插入场景1:红黑树为空树
最简单的一种情景,直接把插入结点作为根结点就行,但注意,根据红黑树性质2:根节点是黑色。还需要把插入结点设为黑色。
处理:把插入结点作为根结点,并把结点设置为黑色。
插入场景2:插入结点的父结点为黑结点
由于插入的结点是红色的,且父节点为黑色节点,并不会影响红黑树的平衡,直接插入即可,无需做自平衡。
处理:直接插入。
插入场景3:插入结点的父结点为红结点
再次回想下红黑树的性质2:根结点是黑色。如果插入的父结点为红结点,那么该父结点不可能为根结点,所以插入结点总是存在祖父结点。这点很重要,因为后续的旋转操作肯定需要祖父结点的参与。
插入场景3.1:叔叔结点存在并且为红结点
从红黑树性质4可以,祖父结点肯定为黑结点,因为不可以同时存在两个相连的红结点。那么此时该插入子树的红黑层数的情况是:黑红红。显然最简单的处理方式是把其改为:红黑红。
实际案例:
祖父节点为根节点:红黑黑
祖父节点不为根节点:
插入场景3.2**:叔叔结点不存在或为黑结点,并且插入结点的父亲结点是祖父结点的左子结点
单纯从插入前来看,也即不算情景3.1自底向上处理时的情况,叔叔结点非红即为叶子结点(Nil)。因为如果叔叔结点为黑结点,而父结点为红结点,那么叔叔结点所在的子树的黑色结点就比父结点所在子树的多了,这不满足红黑树的性质5。后续情景同样如此,不再多做说明了。
前文说了,需要旋转操作时,肯定一边子树的结点多了或少了,需要租或借给另一边。插入显然是多的情况,那么把多的结点租给另一边子树就可以了。
插入场景3.2.1:插入结点是其父结点的左子结点
插入场景3.2.2:叔叔结点不存在或为黑结点,并且插入结点的父亲结点是祖父结点的右子结点
~好了红黑树的添加操作我们就介绍到这儿,下篇我们给大家详细的介绍下红黑树的删除操作哦,欢迎点赞关注加收藏哦!!!
- Google前美女面试官谈程序员面试的技巧和建议.
- Google前美女面试官谈程序员面试的技巧和建议
- FWD:Google前美女面试官谈程序员面试的技巧和建议
- 阿里P8整理Mysql面试题答案,助你面试“脱颖而出”(建议收藏)
- 史上最强多线程面试48题(含答案),建议收藏
- 腾讯面试官送给准程序员的一些建议![转](收藏)
- 记一次Android奇葩面试经历:因为没去过BAT,我被面试官“轰”出门外
- 【面试】吃透了这些Redis知识点,面试官一定觉得你很NB(干货 | 建议珍藏) c#自定义Attribute获取接口实现 纯JS ajax 聊聊DataTable下载 二进制数据的序列化反序...
- Google前美女面试官谈程序员面试的技巧和建议
- 建议收藏 | Web前端面试知识点目录整理
- Google前美女面试官谈程序员面试的技巧和建议
- 写给程序员的 MySQL 面试高频 100 问!建议收藏!
- 从面试官的角度给面试实习的同学一些刷题以外的建议
- 面试运营岗位,总是因为经验不足被面试官拒绝?
- 师兄大厂面试遇到面试官的 Kafka 暴击三连问,快面哭了!
- 【面试】吃透了这些Redis知识点,面试官一定觉得你很NB(干货 | 建议珍藏)
- Java程序员年薪百万,因为他1年走了别人5年的路(技术提炼建议收藏)
- 最全Android面试大纲,建议收藏,年后一定用得着
- Google前美女面试官谈程序员面试的技巧和建议
- Google前美女面试官谈程序员面试的技巧和建议