11. 红黑树
红黑树也是一种自平衡的二叉搜索树,较之 AVL,插入和删除时旋转次数更少
红黑树特性
1.所有节点都有两种颜色:红与黑
2.所有 null 视为黑色
3.红色节点不能相邻
4.根节点是黑色
5.从根到任意一个叶子节点,路径中的黑色节点数一样(黑色完美平衡)补充:黑色叶子结点一般是成对出现,单独出现必定不平衡
11.1 node内部类
private static class Node { int key; Object value; Node left; Node right; Node parent;//父节点 Color color = RED;//颜色 //是否是左孩子,左未true,右为false boolean isLeftChild() { return parent != null && this == parent.left; } //获取叔叔节点 Node uncle() { //有父节点,和父节点的父节点才能有叔叔节点 if (parent == null || parent.parent == null) { return null; } if (parent.isLeftChild()) { //如果父亲为左子节点则返回右子节点, return parent.parent.right; } else { //如果父亲为右子节点则返回左子节点 return parent.parent.left; } } //获取兄弟节点 Node sibling() { if(parent==null){ return null; } if(this.isLeftChild()){ return parent.right; }else { return parent.left; } } }
11.2 判断颜色
//判断颜色 boolean isRed(Node node){ return node!=null&&node.color==RED; } boolean isBlack(Node node){ return node==null||node.color==BLACK; }
11.3 左旋和右旋
//和AVL树的不同 // 右旋1.父(Parent)的处理2.旋转后新根的父子关系方法内处理 void rotateRight(Node pink) { //获取pink的父级几点 Node parent = pink.parent; //旋转 Node yellow = pink.left; Node gree = yellow.right; //右旋之后换色节点的右子结点变为Pink yellow.right = pink; //之前黄色节点的左子结点赋值给pink,完成右旋 pink.left = gree; if (gree != null) { //处理父子关系 gree.parent = pink; } //处理父子关系 yellow.parent = parent; pink.parent = yellow; //如果parent为空则根节点为yellow if (parent == null) { root = yellow; } else if (parent.left == pink) { //处理父子关系,yellow代替pink parent.left = yellow; } else { parent.right = yellow; } } void rotateLeft(Node pink) { //获取pink的父级几点 Node parent = pink.parent; //旋转 Node yellow = pink.right; Node gree = yellow.left; //右旋之后换色节点的右子结点变为Pink yellow.left = pink; //之前黄色节点的左子结点赋值给pink,完成右旋 pink.right = gree; if (gree != null) { //处理父子关系 gree.parent = pink; } //处理父子关系 yellow.parent = parent; pink.parent = yellow; //如果parent为空则根节点为yellow if (parent == null) { root = yellow; } else if (parent.right == pink) { //处理父子关系,yellow代替pink parent.right = yellow; } else { parent.left = yellow; } }
11.4 新增或更新
//新增或更新 void put(int key, Object value) { Node p = root; Node parent = null; while (p != null) { parent = p; if (key < p.key) { //向左找 p = p.left; } else if (key > p.key) { p = p.right; } else { //更新 p.value = value; return; } } //插入 Node interestNode = new Node(key, value); //如果parent为空 if (parent == null) { root = interestNode; } else if (key < parent.key) { parent.left = interestNode; interestNode.parent = parent; } else { parent.right = interestNode; interestNode.parent = parent; } fixRedRed(interestNode); } /* * 1.所有节点都有两种颜色:红与黑 * 2.所有 null 视为黑色 * 3.红色节点不能相邻 * 4.根节点是黑色 * 5.从根到任意一个叶子节点,路径中的黑色节点数一样(黑色完美平衡) * */ //修复红红不平衡 void fixRedRed(Node x) { /* * 插入节点均视为红色 * 插入节点的父亲为红色 * 触发红红相邻 * case 3:叔叔为红色 * case 4:叔叔为黑色 * */ //case 1:插入节点为根节点,将根节点变黑 if (x == root) { x.color = BLACK; return; } //case 2:插入节点的父亲若为黑色树的红黑性质不变,无需调整 if (isBlack(x.parent)) { return; } Node parent = x.parent; Node uncle = x.uncle(); Node grandParent = parent.parent; //插入节点的父亲为红色 if (isRed(uncle)) { //红红相邻叔叔为红色时(仅通过变色即可) /* * 为了保证黑色平衡,连带的叔叔也变为黑色· * 祖父如果是黑色不变,会造成这颗子树黑色过多,因此祖父节点变为红色祖父如果变成红色, * 可能会接着触发红红相邻,因此对将祖父进行递归调整,祖父的父亲变为黑色 * */ parent.color = BLACK; uncle.color = BLACK; grandParent.color = RED; fixRedRed(grandParent); return; } else { //触发红红相邻叔叔为黑色 /* * 1.父亲为左孩子,插入节点也是左孩子,此时即 LL不平衡(右旋一次) * 2.父亲为左孩子,插入节点是右孩子,此时即 LR不平衡(父亲左旋,祖父右旋) * 3.父亲为右孩子,插入节点也是右孩子,此时即 RR不平衡(左旋一次) * 4.父亲为右孩子,插入节点是左孩子,此时即 RL不平衡(父亲右旋,祖父左旋) * */ if (parent.isLeftChild() && x.isLeftChild()) { //如果父亲为左子节点,查询节点也为左子结点即为LL(祖父右旋处理) //父节点变黑(和叔叔节点同为黑色) parent.color = BLACK; //祖父节点变红 grandParent.color = RED; rotateRight(grandParent); } else if (parent.isLeftChild() && !x.isLeftChild()) { //parent为左子节点,插入节点为右子节点,LR(父亲左旋,祖父右旋) rotateLeft(parent); //插入节点变为黑色 x.color=BLACK; //祖父变为红色 grandParent.color=RED; rotateRight(grandParent); } else if (!parent.isLeftChild()&&!x.isLeftChild()) { //父节点变黑(和叔叔节点同为黑色) parent.color = BLACK; //祖父节点变红 grandParent.color = RED; rotateLeft(grandParent); }else if(!parent.isLeftChild()&&x.isLeftChild()){ rotateRight(parent); //插入节点变为黑色 x.color=BLACK; //祖父变为红色 grandParent.color=RED; rotateLeft(grandParent); } } }
11.5 删除
//删除 /* * 删除 * 正常删、会用到李代桃僵技巧、遇到黑黑不平衡进行调整 * */ public void remove(int key) { Node deleted = find(key); if (deleted == null) { return; } doRemove(deleted); } //检测双黑节点删除 /* * * 删除节点和剩下节点都是黑触发双黑,双黑意思是,少了一个黑 * case 3:删除节点或剩余节点的兄弟为红此时两个侄子定为黑 * case 4:删除节点或剩余节点的兄弟、和兄弟孩子都为黑 * case 5:删除节点的兄弟为黑,至少一个红侄子 * * */ private void fixDoubleBlack(Node x) { //把case3处理为case4和case5 if (x == root) { return; } Node parent = x.parent; Node sibling = x.sibling(); if (sibling.color == RED) { //进入case3 if (x.isLeftChild()) { //如果是做孩子就进行左旋 rotateLeft(parent); } else { //如果是右孩子就进行右旋 rotateRight(parent); } //父亲颜色变为红色(父亲变色前肯定为黑色) parent.color = RED; //兄弟变为黑色 sibling.color = BLACK; //此时case3 已经被转换为 case4或者case5 fixDoubleBlack(x); return; } //兄弟为黑 //两个侄子都为黑 if (sibling != null) { if (isBlack(sibling.left) && isBlack(sibling.right)) { /* * case 4:被调整节点的兄弟为黑,两个侄于都为黑 * 将兄弟变红, 目的是将删除节点和兄弟那边的黑色高度同时减少1 * 如果父亲是红,则需将父亲变为黑,避免红红,此时路径黑节点数目不变 * 如果父亲是黑,说明这条路径则少了一个黑,再次让父节点触发双黑 * */ //将兄弟变红, 目的是将删除节点和兄弟那边的黑色高度同时减少1 sibling.color = RED; if (isRed(parent)) { // 如果父亲是红,则需将父亲变为黑,避免红红,此时路径黑节点数目不变 parent.color = BLACK; } else { //如果父亲是黑,说明这条路径则少了一个黑,再次让父节点触发双黑 fixDoubleBlack(parent); } } else { //CASE5 至少有一个侄子不是黑色 /* * * case 5:被调整节点的兄弟为黑 * 如果兄弟是左孩子,左侄子是红 LL 不平衡 * 如果兄弟是左孩子,右侄子是红 LR 不平衡 * 如果兄弟是右孩子,右侄于是红 RR 不平衡 * 如果兄弟是右孩子,左侄于是红 RL 不平衡 * */ if(sibling.isLeftChild()&&isRed(sibling.left)){ // 如果兄弟是左孩子,左侄子是红 LL 不平衡 rotateRight(parent); //兄弟的做孩子变黑 sibling.left.color=BLACK; //兄弟变成父亲的颜色 sibling.color= parent.color; //父亲变黑 parent.color=BLACK; }else if(sibling.isLeftChild()&&isRed(sibling.right)){ //如果兄弟是左孩子,右侄子是红 LR 不平衡 //变色必须在上 否则旋转后会空指针 //兄弟的右孩子变为父节点颜色 sibling.right.color= parent.color; //父节点变黑 parent.color=BLACK; rotateLeft(sibling); rotateRight(parent); } } } else { fixDoubleBlack(parent); } } private void doRemove(Node deleted) { Node replaced = findReplaced(deleted); Node parent = deleted.parent; if (replaced == null) { //没有子节点 /* case1:删的是根节点,没有子节点 * 删完了,直接将 root=null * */ if (deleted == root) { root = null; } else { /* * Case2:删的是黑,剩下的是红剩下这个红节点变黑 * */ if (isBlack(deleted)) { //复杂处理,先调整平衡再删除 fixDoubleBlack(deleted); } else { //红色不处理 } //删除的不是根节点且没有孩子 if (deleted.isLeftChild()) { parent.left = null; } else { parent.right = null; } deleted.parent = null; } return; } else if (replaced.left == null || replaced.right == null) { //有一个子节点 /* case1:删的是根节点,有一个子节点 * 用剩余节点替换了根节点的 key,Value,根节点孩子=null,颜色保持黑色 不变 * */ if (deleted == root) { root.key = replaced.key; root.value = replaced.value; root.left = root.right = null; } else { //删除的不是根节点但是有一个孩子 if (deleted.isLeftChild()) { parent.left = replaced; } else { parent.right = replaced; } replaced.parent = parent; deleted.right = deleted.left = deleted.parent = null; if (isBlack(deleted) && isBlack(replaced)) { //如果删除的是黑色剩下的也是黑色,需要复杂处理,先删除再平衡 fixDoubleBlack(replaced); } else { /* * Case2:删的是黑,剩下的是红剩下这个红节点变黑 * */ replaced.color = BLACK; } } return; } else { //两个子节点 //交换删除节点和删除节点的后几点的key,value,这 // 样被删除节点就只有一个子节点或没有子节点了 int t = deleted.key; deleted.key = replaced.key; replaced.key = t; Object v = deleted.value; deleted.value = replaced.value; replaced.value = v; doRemove(replaced); return; } } private Node find(int key) { Node p = root; while (p != null) { if (key > p.key) { //key更大向右找 p = p.right; } else if (key < p.key) { //向左找 p = p.left; } else { return p; } } return null; } //查找删除后的剩余节点 private Node findReplaced(Node deleted) { if (deleted.left == null & deleted.right == null) { //没有子节点 return null; } else if (deleted.left == null) { return deleted.right; } else if (deleted.right == null) { return deleted.left; } else { Node s = deleted.right; while (s != null) { //右子树的最小值 s = s.left; } return s; } } }
11.6 完整代码
package org.alogorithm.tree; import static org.alogorithm.tree.RedBlackTree.Color.BLACK; import static org.alogorithm.tree.RedBlackTree.Color.RED; public class RedBlackTree { enum Color {RED, BLACK} private Node root; private static class Node { int key; Object value; Node left; Node right; Node parent;//父节点 Color color = RED;//颜色 public Node() { } public Node(int key, Object value) { this.key = key; this.value = value; } //是否是左孩子,左未true,右为false boolean isLeftChild() { return parent != null && this == parent.left; } //获取叔叔节点 Node uncle() { //有父节点,和父节点的父节点才能有叔叔节点 if (parent == null || parent.parent == null) { return null; } if (parent.isLeftChild()) { //如果父亲为左子节点则返回右子节点, return parent.parent.right; } else { //如果父亲为右子节点则返回左子节点 return parent.parent.left; } } //获取兄弟节点 Node sibling() { if (parent == null) { return null; } if (this.isLeftChild()) { return parent.right; } else { return parent.left; } } } //判断颜色 boolean isRed(Node node) { return node != null && node.color == RED; } boolean isBlack(Node node) { return node == null || node.color == BLACK; } //和AVL树的不同 // 右旋1.父(Parent)的处理2.旋转后新根的父子关系方法内处理 void rotateRight(Node pink) { //获取pink的父级几点 Node parent = pink.parent; //旋转 Node yellow = pink.left; Node gree = yellow.right; //右旋之后换色节点的右子结点变为Pink yellow.right = pink; //之前黄色节点的左子结点赋值给pink,完成右旋 pink.left = gree; if (gree != null) { //处理父子关系 gree.parent = pink; } //处理父子关系 yellow.parent = parent; pink.parent = yellow; //如果parent为空则根节点为yellow if (parent == null) { root = yellow; } else if (parent.left == pink) { //处理父子关系,yellow代替pink parent.left = yellow; } else { parent.right = yellow; } } void rotateLeft(Node pink) { //获取pink的父级几点 Node parent = pink.parent; //旋转 Node yellow = pink.right; Node gree = yellow.left; //右旋之后换色节点的右子结点变为Pink yellow.left = pink; //之前黄色节点的左子结点赋值给pink,完成右旋 pink.right = gree; if (gree != null) { //处理父子关系 gree.parent = pink; } //处理父子关系 yellow.parent = parent; pink.parent = yellow; //如果parent为空则根节点为yellow if (parent == null) { root = yellow; } else if (parent.right == pink) { //处理父子关系,yellow代替pink parent.right = yellow; } else { parent.left = yellow; } } //新增或更新 void put(int key, Object value) { Node p = root; Node parent = null; while (p != null) { parent = p; if (key < p.key) { //向左找 p = p.left; } else if (key > p.key) { p = p.right; } else { //更新 p.value = value; return; } } //插入 Node interestNode = new Node(key, value); //如果parent为空 if (parent == null) { root = interestNode; } else if (key < parent.key) { parent.left = interestNode; interestNode.parent = parent; } else { parent.right = interestNode; interestNode.parent = parent; } fixRedRed(interestNode); } /* * 1.所有节点都有两种颜色:红与黑 * 2.所有 null 视为黑色 * 3.红色节点不能相邻 * 4.根节点是黑色 * 5.从根到任意一个叶子节点,路径中的黑色节点数一样(黑色完美平衡) * */ //修复红红不平衡 void fixRedRed(Node x) { /* * 插入节点均视为红色 * 插入节点的父亲为红色 * 触发红红相邻 * case 3:叔叔为红色 * case 4:叔叔为黑色 * */ //case 1:插入节点为根节点,将根节点变黑 if (x == root) { x.color = BLACK; return; } //case 2:插入节点的父亲若为黑色树的红黑性质不变,无需调整 if (isBlack(x.parent)) { return; } Node parent = x.parent; Node uncle = x.uncle(); Node grandParent = parent.parent; //插入节点的父亲为红色 if (isRed(uncle)) { //红红相邻叔叔为红色时(仅通过变色即可) /* * 为了保证黑色平衡,连带的叔叔也变为黑色· * 祖父如果是黑色不变,会造成这颗子树黑色过多,因此祖父节点变为红色祖父如果变成红色, * 可能会接着触发红红相邻,因此对将祖父进行递归调整,祖父的父亲变为黑色 * */ parent.color = BLACK; uncle.color = BLACK; grandParent.color = RED; fixRedRed(grandParent); return; } else { //触发红红相邻叔叔为黑色 /* * 1.父亲为左孩子,插入节点也是左孩子,此时即 LL不平衡(右旋一次) * 2.父亲为左孩子,插入节点是右孩子,此时即 LR不平衡(父亲左旋,祖父右旋) * 3.父亲为右孩子,插入节点也是右孩子,此时即 RR不平衡(左旋一次) * 4.父亲为右孩子,插入节点是左孩子,此时即 RL不平衡(父亲右旋,祖父左旋) * */ if (parent.isLeftChild() && x.isLeftChild()) { //如果父亲为左子节点,查询节点也为左子结点即为LL(祖父右旋处理) //父节点变黑(和叔叔节点同为黑色) parent.color = BLACK; //祖父节点变红 grandParent.color = RED; rotateRight(grandParent); } else if (parent.isLeftChild() && !x.isLeftChild()) { //parent为左子节点,插入节点为右子节点,LR(父亲左旋,祖父右旋) rotateLeft(parent); //插入节点变为黑色 x.color = BLACK; //祖父变为红色 grandParent.color = RED; rotateRight(grandParent); } else if (!parent.isLeftChild() && !x.isLeftChild()) { //父节点变黑(和叔叔节点同为黑色) parent.color = BLACK; //祖父节点变红 grandParent.color = RED; rotateLeft(grandParent); } else if (!parent.isLeftChild() && x.isLeftChild()) { rotateRight(parent); //插入节点变为黑色 x.color = BLACK; //祖父变为红色 grandParent.color = RED; rotateLeft(grandParent); } } } //删除 /* * 删除 * 正常删、会用到李代桃僵技巧、遇到黑黑不平衡进行调整 * */ public void remove(int key) { Node deleted = find(key); if (deleted == null) { return; } doRemove(deleted); } //检测双黑节点删除 /* * * 删除节点和剩下节点都是黑触发双黑,双黑意思是,少了一个黑 * case 3:删除节点或剩余节点的兄弟为红此时两个侄子定为黑 * case 4:删除节点或剩余节点的兄弟、和兄弟孩子都为黑 * case 5:删除节点的兄弟为黑,至少一个红侄子 * * */ private void fixDoubleBlack(Node x) { //把case3处理为case4和case5 if (x == root) { return; } Node parent = x.parent; Node sibling = x.sibling(); if (sibling.color == RED) { //进入case3 if (x.isLeftChild()) { //如果是做孩子就进行左旋 rotateLeft(parent); } else { //如果是右孩子就进行右旋 rotateRight(parent); } //父亲颜色变为红色(父亲变色前肯定为黑色) parent.color = RED; //兄弟变为黑色 sibling.color = BLACK; //此时case3 已经被转换为 case4或者case5 fixDoubleBlack(x); return; } //兄弟为黑 //两个侄子都为黑 if (sibling != null) { if (isBlack(sibling.left) && isBlack(sibling.right)) { /* * case 4:被调整节点的兄弟为黑,两个侄于都为黑 * 将兄弟变红, 目的是将删除节点和兄弟那边的黑色高度同时减少1 * 如果父亲是红,则需将父亲变为黑,避免红红,此时路径黑节点数目不变 * 如果父亲是黑,说明这条路径则少了一个黑,再次让父节点触发双黑 * */ //将兄弟变红, 目的是将删除节点和兄弟那边的黑色高度同时减少1 sibling.color = RED; if (isRed(parent)) { // 如果父亲是红,则需将父亲变为黑,避免红红,此时路径黑节点数目不变 parent.color = BLACK; } else { //如果父亲是黑,说明这条路径则少了一个黑,再次让父节点触发双黑 fixDoubleBlack(parent); } } else { //CASE5 至少有一个侄子不是黑色 /* * * case 5:被调整节点的兄弟为黑 * 如果兄弟是左孩子,左侄子是红 LL 不平衡 * 如果兄弟是左孩子,右侄子是红 LR 不平衡 * 如果兄弟是右孩子,右侄于是红 RR 不平衡 * 如果兄弟是右孩子,左侄于是红 RL 不平衡 * */ if(sibling.isLeftChild()&&isRed(sibling.left)){ // 如果兄弟是左孩子,左侄子是红 LL 不平衡 rotateRight(parent); //兄弟的做孩子变黑 sibling.left.color=BLACK; //兄弟变成父亲的颜色 sibling.color= parent.color; //父亲变黑 parent.color=BLACK; }else if(sibling.isLeftChild()&&isRed(sibling.right)){ //如果兄弟是左孩子,右侄子是红 LR 不平衡 //变色必须在上 否则旋转后会空指针 //兄弟的右孩子变为父节点颜色 sibling.right.color= parent.color; //父节点变黑 parent.color=BLACK; rotateLeft(sibling); rotateRight(parent); } } } else { fixDoubleBlack(parent); } } private void doRemove(Node deleted) { Node replaced = findReplaced(deleted); Node parent = deleted.parent; if (replaced == null) { //没有子节点 /* case1:删的是根节点,没有子节点 * 删完了,直接将 root=null * */ if (deleted == root) { root = null; } else { /* * Case2:删的是黑,剩下的是红剩下这个红节点变黑 * */ if (isBlack(deleted)) { //复杂处理,先调整平衡再删除 fixDoubleBlack(deleted); } else { //红色不处理 } //删除的不是根节点且没有孩子 if (deleted.isLeftChild()) { parent.left = null; } else { parent.right = null; } deleted.parent = null; } return; } else if (replaced.left == null || replaced.right == null) { //有一个子节点 /* case1:删的是根节点,有一个子节点 * 用剩余节点替换了根节点的 key,Value,根节点孩子=null,颜色保持黑色 不变 * */ if (deleted == root) { root.key = replaced.key; root.value = replaced.value; root.left = root.right = null; } else { //删除的不是根节点但是有一个孩子 if (deleted.isLeftChild()) { parent.left = replaced; } else { parent.right = replaced; } replaced.parent = parent; deleted.right = deleted.left = deleted.parent = null; if (isBlack(deleted) && isBlack(replaced)) { //如果删除的是黑色剩下的也是黑色,需要复杂处理,先删除再平衡 fixDoubleBlack(replaced); } else { /* * Case2:删的是黑,剩下的是红剩下这个红节点变黑 * */ replaced.color = BLACK; } } return; } else { //两个子节点 //交换删除节点和删除节点的后几点的key,value,这 // 样被删除节点就只有一个子节点或没有子节点了 int t = deleted.key; deleted.key = replaced.key; replaced.key = t; Object v = deleted.value; deleted.value = replaced.value; replaced.value = v; doRemove(replaced); return; } } private Node find(int key) { Node p = root; while (p != null) { if (key > p.key) { //key更大向右找 p = p.right; } else if (key < p.key) { //向左找 p = p.left; } else { return p; } } return null; } //查找删除后的剩余节点 private Node findReplaced(Node deleted) { if (deleted.left == null & deleted.right == null) { //没有子节点 return null; } else if (deleted.left == null) { return deleted.right; } else if (deleted.right == null) { return deleted.left; } else { Node s = deleted.right; while (s != null) { //右子树的最小值 s = s.left; } return s; } } }