Java基础 - 红黑树

来源:互联网 发布:智能垃圾桶 知乎 编辑:程序博客网 时间:2024/05/21 02:37

package com.yc.tree;import java.util.ArrayDeque;import java.util.ArrayList;import java.util.Deque;import java.util.List;/** * @author wb * @param <T> *  * 排序二叉树虽然可以快速检索,但在最坏的情况下,如果插入的节点集本身就是有序的,要么是由小到大排列,要么是由大到小排列, * 那么最后的排序二叉树将得到链表:所有节点只有左子节点,或者所有的节点只有右子节点。在这种情况下,排序二叉树变成了普通链表, * 其检索效率就会很低。 *  * 为了改变排序二叉树存在的这种不足,Rudolf Bayer在1972年发明了另一种改进后的排序二叉树——红黑树,他将这种排序二叉树 * 称为“对称二叉B树”,而红黑树这个名字则由Leo J.Guibas 和  Robert Sedgewick 在1978年首次提出。 *  * 红黑树是一棵更高效的检索二叉树,因此常常用来实现关联数组。典型的,JDK提供的集合类TreeMap本身就是一棵红黑树的实现。 *  * 红黑树在原有的排序二叉树上增加了如下几个要求: * 性质(1):每个节点要么是红色,要么是黑色。 * 性质(2):根节点永远是黑色。 * 性质(3):所有的叶子节点都是空节点(即null),且是黑色的。 * 性质(4):每个红色节点的两个子节点都是黑色的。(从每个叶子节点到根的路径上不会有两个连续的红色节点) * 性质(5):从任一节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点。 *  * 根据性质5,红黑树从根节点到每个叶子节点的路径都包含相同数量的黑色节点,因此从根节点到叶子节点的路径中包含的黑色节点数被 * 称为树的“黑色高度(black-height)”。 * 性质4则保证了从根节点到叶子节点的最长路径的长度不会超过任何其它路径的2倍。假设有一棵黑色高度为3的红黑树,从根节点到叶子节点的最短路径 * 长度是2,改路径上全是黑色节点(黑色节点-黑色节点-黑色节点)。最长路径也只可能是4,在每个黑色节点之间插入一个红色节点(黑色节点-红色节点- * 黑色节点-红色节点-黑色节点),性质4保证绝不可能插入更多的红色节点。由此可见,红黑树中最长的路径就是一条红黑交替的路径。 *  * 由此可以得出结论:对于给定的黑色高度为 N 的红黑树,从根到叶子节点的最短路径长度为 N-1 ,最长路径的长度为 2*(N-1)。 ******************************************************************************* * 提示:排序二叉树的深度直接影响了检索的性能。正如前面指出的,当插入节点本身就是有小到大排列时,排序二叉树将变成* * 一个链表,这种排序二叉树的检索性能最低:N 个节点的二叉树深度就是 N-1;* ******************************************************************************* * *红黑树通过上面这种限制来保证它大致是平衡的——因为红黑树的高度不会无限增高,这样能保证红黑树在最坏的情况下都是高效的,不会出现普通排序二叉树的情况。 * ************************************************************************** *注意:红黑树并不是真正的平衡二叉树,但在实际应用中,红黑树的统计性能要高于平衡二叉树,但在极端情况下略差。* ************************************************************************** * *由于红黑树是一种特殊的排序二叉树,因此对红黑树的只读操作和普通排序二叉树的只读操作完全相同,只是红黑树保持了大致平衡, *因此检索性能更好。但在红黑树上进行插入操作和删除操作会导致树不在符合红黑树的特征,因此插入操作和删除操作都需要进行一定的维护, *以保证插入节点、删除节点后的树依然是红黑树。 * *插入操作: *①以排序二叉树的方法插入新节点,并将它设为红色。(如果设为黑色,就会导致根节点到叶子节点的路径上多一个额外的黑色节点,那么所经过该新添加黑色节点的 *路径的长度都会比其他的 大1,这样将会导致很难调整。但是设为红色节点后,可能会导致出现两个连续的红色节点,在通过颜色调换和树旋转来调整即可)。 *②这种颜色调换和树旋转就复杂了,下面将分情况进行介绍。介绍中将新插入的节点定义为N节点,把N节点的父节点定义为P节点,把P节点的兄弟节点定义为U节点, *把P节点的父节点定义为G节点。(在插入操作中,红黑树的性质1和性质3都是不变的,因此无需考虑这两个性质) *Ⅰ、情形1:新节点N是树的根节点,没有父节点。 *在这种情况下,直接将他设为黑色以满足性质2。 *Ⅱ、情形2:新节点N的父节点P是黑色的。 *在这种情况下,新插入的节点是红色的,因此满足性质4(我只关心父节点)。而且因为新节点N有两个黑色叶子节点,但是由于新节点N是红色的, *通过他的每个子节点的路径依然保持相同的黑色节点数,因此依然满足性质5. *Ⅲ、情形3:父节点P和父节点的兄弟节点U都是红色的。 *在这种情况下,程序应该将P节点、U节点都设置为黑色,并将P节点的父节点G设置为红色(用来保持性质5)。现在,新节点N有了一个黑色的父节点P。 *由于从P节点、U节点到根节点的任何路径都必须通过G节点,这路径上的黑色节点数目没有发生改变(原来有叶子节点和G节点两个黑色节点,现在有叶子 *和P两个黑色节点)。 *经过上面的处理后,红色节点G的父节点也有可能是红色的,这就违反了性质4,因此还需要对G节点递归的进行整个过程(把G节点当成新插入的节点进行调整)。 *下图显示了这种操作: *   G (B)   G (R) *   ││   ││ *   (R) P ────────┘└────── U (R) ---> (B) P ────────┘└────── U (B)  *  ││  null ──┘└── null││null ──┘└── null *  (B) N ──┘└── null(R) N ──┘└── null * null ──┘└── nullnull ──┘└── null *图11.28 插入节点后进行颜色调换 *虽然图11.28中绘制的是新节点N作为父节点P的左子节点的情形,但其实新节点N作为父节点P的右子节点的情形与图11.28完全相同。 * *Ⅳ、情形4:父节点P是红色的,而其兄弟节点U是黑色的或缺少;且新节点N是父节点P的右子节点,而父节点P又是其父节点G的左子节点。 *在这种情况下,对新节点和其父节点进行一次左旋转。接着,按情形5处理以前的父节点P(也就是把P当成新插入的节点)。这将导致某些路径 *通过他们以前不通过的新节点N或父节点P其中之一,但是这两个节点都是红色的,因此不会影响性质5. *下图显示了对情形4的处理: * * G (B)G (B) *││││ *  (R) P ────────┘└────── U (B)    --->  (R) N ────────┘└────── U (B) * ││  null ──┘└── null ││    null ──┘└── null *  null ──┘└── N (R) (R) P ──┘└── null *   null ──┘└── null  null ──┘└── null *图11.29 插入节点后的树旋转 *图11.29中P节点是G节点的左子节点,如果P节点是其父节点G的右子节点,那么上面的处理情况应该左、右对调一下。 * *Ⅴ、情形5:父节点P是红色的,而其兄弟节点U是黑色的或缺少;且新节点N是父节点P的左子节点,而父节点P又是其父节点G的左子节点。 *在这种情形下,需要对节点G进行一次右旋转。在旋转产生的树中,以前的父节点P现在是新节点N和节点G的父节点。由于以前的节点G是黑色的(否则父节点P *不可能是红色的),切换以前的父节点P和节点G的原色,使之满足性质4,。性质5也仍然保持满足,因为通过这三个节点中任何一个的所有路径以前都通过节点G *现在他们都通过以前的父节点P。在各自的情况下,这都是三个节点中唯一的黑色节点。 *下图显示了对情形5的处理: * *G (B)P (B) *   ││││ * (R) P ────────┘└────── U (B)  --->  (R) N ─────┘└────── G (R) *││  null ──┘└── null null ──┘└── null  │ *(R) N ──┘└── null└────── U (B) * null ──┘└── nullnull ──┘└── null  *图11.30 插入节点后的颜色调换、树旋转 *图11.30中的P节点是G节点的左子节点,如果P节点是其父节点的右子节点,那么上面的处理情况应该左、右对调一下。 * * *删除操作: *红黑树的删除操作比插入操作要稍微复杂一些,实际上也可按如下步骤进行: *①以排序二叉树的方法删除指定节点。 *②进行颜色调换和树旋转,使之满足红黑树特征。 *关于红黑树的删除操作要进行的颜色调换和树旋转可以参考前面的红黑树插入时各个情况的处理。 * *下面给出一棵红黑树的Java实现(更具体的建议大家去看Java集合框架中的TreeMap实现类,它就是红黑树在Java中的最好实现吧): */@SuppressWarnings("rawtypes")public class RedBlackTree <T extends Comparable>{private static final boolean RED = false;private static final boolean BLACK = true;static class Node{Object data;Node parent;Node left;Node right;//节点默认颜色是黑色boolean color = BLACK;public Node(){}public Node(Object data, Node parent, Node left, Node right){this.data = data;this.parent = parent;this.left = left;this.right = right;}public String toString(){return "[data:" + data + ", color:" + color + "]";}public boolean equals(Object obj) {if (this == obj){return true;}if(Node.class == obj.getClass()){Node target = (Node)obj;return data.equals(target.data) &&parent == target.parent &&left == target.left &&right == target.right;}return false;}}private Node root;public RedBlackTree(){root = null;}public RedBlackTree(T data){root = new Node(data, null, null, null);}/** * 添加元素 * @param data */@SuppressWarnings("unchecked")public void add(T data){if(root == null){root = new Node(data, null, null, null);}else{Node current = root;Node parent = null;int  tmp = 0;do{parent = current;tmp = data.compareTo(current.data);if(tmp > 0){current = current.right;}else{current = current.left;}}while( current != null);//此时的parent指向的就是 要添加节点的父节点。Node newNode = new Node(data, parent, null, null);if(tmp > 0){parent.right = newNode;}else{parent.left = newNode;}fixAfterAdd(newNode);}}/** * 删除元素 * @param data */public void remove(T data){//获取要删除的元素节点Node target = getNode(data);//如果左、右子树都不为空if(target.left != null && target.right != null){Node s = target.left;while( s.right != null){s = s.right;}//用s节点代替target节点target.data = s.data;target = s;}//开始修复它的替换节点,如果该替换节点不为null//个人觉得这段代码一石二鸟Node replacement = (target.left != null ? target.left : target.right);if(replacement != null){replacement.parent = target.parent;if(target.parent == null){root = replacement;}else if(target == target.parent.left){target.parent.left = replacement;}else{target.parent.right = replacement;}target.parent = target.left = target.right = null;if(target.color == BLACK){fixAfterRemove(replacement);}}else if(target.parent == null){root = null;}else{if(target.color == BLACK){fixAfterRemove(target);}if(target.parent != null){if(target == target.parent.left){target.parent.left = null;}else if(target == target.parent.right){target.parent.right = null;}target.parent = null;}}}/** * 插入新节点 newNode 后对红黑树的维护 * @param newNode */private void fixAfterAdd(Node node) {node.color = RED;//直到node节点的父节点不是根,且node节点的父节点不为红色while(node != null && node != root && node.parent.color == RED){//如果node的父节点P是其父节点G的左子节点if(parentOf(node) == leftChildOf(parentOf(parentOf(node)))){//获取node节点的父节点P的兄弟节点UNode uNode = rightChildOf(parentOf(parentOf(node)));if(colorOf(uNode) == RED){//如果node父节点P的兄弟节点的颜色为红色(情形3)//将node父节点P设为黑色setColor(parentOf(node), BLACK);//将node父节点P的兄弟节点U设为黑色setColor(uNode, BLACK);//将node父节点P的父节点G设为红色setColor(parentOf(parentOf(node)), RED);//情景3中可能G的父节点也为红色,所以循环递归调试node = parentOf(parentOf(node));}else{//如果node父节点P的兄弟节点的颜色为黑色(情形4或5)if(node == rightChildOf(parentOf(node))){//(情形4)node = parentOf(node);rotateLeft(node);}//将node的父节点(可能是N也可能是P)设为黑色setColor(parentOf(node), BLACK);//将node的父节点的父节点G设为红色setColor(parentOf(parentOf(node)), RED);//将G节点右旋转rotateRight(parentOf(parentOf(node)));}}//如果node的父节点P是其父节点G的右子节点else{Node uNode = leftChildOf(parentOf(parentOf(node)));if(colorOf(uNode) == RED){setColor(parentOf(node), BLACK);setColor(uNode, BLACK);setColor(parentOf(parentOf(node)), RED);node = parentOf(parentOf(node));}else{if(node == leftChildOf(parentOf(node))){node = parentOf(node);rotateRight(node);}setColor(parentOf(node), BLACK);setColor(parentOf(parentOf(node)), RED);rotateLeft(parentOf(parentOf(node)));}}}root.color = BLACK;}/** * 删除节点后对红黑树的维护 * @param replacement :要删除节点的左或右子节点 */private void fixAfterRemove(Node node) {//至到 node 不是根节点,且node节点的颜色是黑色的while(node != root && colorOf(node) == BLACK){//如果node是其父节点的左子节点if(node == leftChildOf(parentOf(node))){Node uNode = rightChildOf(parentOf(node));//如果node父节点的兄弟节点是红色的if(colorOf(uNode) == RED){//设node父节点的兄弟节点为黑色setColor(uNode, BLACK);//设node节点的父节点为红色setColor(parentOf(node), RED);//以node节点的父节点向左旋转rotateLeft(parentOf(node));//再次将node节点的兄弟节点U设为node父节点的右子节点uNode = rightChildOf(parentOf(node));}//如果uNode的两个子节点都是黑色if(colorOf(leftChildOf(uNode)) == BLACK && colorOf(rightChildOf(uNode)) == BLACK){//将uNode设为红色setColor(uNode, RED);node = parentOf(node);}else{//如果只有uNode的右子节点是黑色if(colorOf(rightChildOf(uNode)) == BLACK){//将uNode的左子节点也设为黑色setColor(leftChildOf(uNode), BLACK);//将uNode设为红色setColor(uNode, RED);rotateRight(uNode);uNode = rightChildOf(parentOf(node));}//设置uNode的颜色和node父节点的颜色相同setColor(uNode, colorOf(parentOf(node)));//将node的父节点设为黑色setColor(parentOf(node), BLACK);//将uNode的右子节点设为黑色setColor(rightChildOf(uNode), BLACK);rotateLeft(parentOf(node));node = root;}}//如果node是其父节点的右子节点else{Node uNode = leftChildOf(parentOf(node));if(colorOf(uNode) == RED){setColor(uNode, BLACK);setColor(parentOf(node), RED);rotateRight(parentOf(node));uNode = leftChildOf(parentOf(node));}if(colorOf(leftChildOf(uNode)) == BLACK && colorOf(rightChildOf(uNode)) == BLACK){setColor(uNode, RED);node = parentOf(node);}else{if(colorOf(leftChildOf(uNode)) == BLACK){setColor(rightChildOf(uNode), BLACK);setColor(uNode, RED);rotateLeft(uNode);uNode = leftChildOf(parentOf(node));}setColor(uNode, colorOf(parentOf(node)));setColor(parentOf(node), BLACK);setColor(leftChildOf(uNode), BLACK);rotateRight(parentOf(node));node = root;}}}setColor(node, BLACK);}/** * 左旋转 * 执行如下转换 * pr * ││ * └──r->   p──┘ *     │ │ *  q──┘ └──q * @param node */private void rotateLeft(Node p) {if(p != null){Node r = p.right; //p的右子节点Node q = r.left;//p的右子节点的左子节点p.right = q;if(q != null){q.parent = p;}r.parent = p.parent;if(p.parent == null){root = r;}else if(p == p.parent.left){p.parent.left = r;}else{p.parent.right = r;}p.parent = r;r.left = p;}}/** * 右旋转 * 执行如下转换 * pl * ││ *  l──┘->      └──p *   │    │ *   └──qq──┘ * @param node */private void rotateRight(Node p) {if(p != null){Node l = p.left; //p的左子节点Node q = l.right;//p的左子节点的右子节点p.left = q;if(q != null){q.parent = p;}l.parent = p.parent;if(p.parent == null){root = l;}else if(p == p.parent.left){p.parent.left = l;}else{p.parent.right = l;}p.parent = l;l.right = p;}}//返回指定节点的颜色private boolean colorOf(Node node){return node == null ? BLACK : node.color;}//为指定节点设置颜色private void setColor(Node node, boolean color){if(node != null){node.color = color;}}//返回指定节点的父节点private Node parentOf(Node node){return node == null ? null : node.parent;}//返回指定节点的左子节点private Node leftChildOf(Node node){return node == null ? null : node.left;}//返回指定节点的右子节点private Node rightChildOf(Node node){return node == null ? null : node.right;}/** * 获取指定元素的节点 * @param data * @return */@SuppressWarnings("unchecked")private Node getNode(T data) {Node current = root;int tmp = 0;while(current != null){tmp = data.compareTo(current.data);if(tmp > 0){current = current.right;}else if(tmp < 0){current = current.left;}else{return current;}}return null;}/** * 广度优先遍历 * @return */public List<Node> breadthFirstSearch(){List<Node> nodes = new ArrayList<Node>();Deque<Node> deque = new ArrayDeque<Node>();if(root != null){deque.offer(root);}while(!deque.isEmpty()){Node tmp = deque.poll();nodes.add(tmp);if(tmp.left != null){deque.offer(tmp.left);}if(tmp.right != null){deque.offer(tmp.right);}}return nodes;}/** * 递归之中度遍历 * @return */public List<Node> cLDR(){return cLDR(root);}public List<Node> cLDR(Node node){List<Node> nodes = new ArrayList<Node>();if(node.left != null){nodes.addAll( cLDR(node.left) );}nodes.add(node);if(node.right != null){nodes.addAll( cLDR(node.right) );}return nodes;}}




测试代码如下:

package com.yc.test;import com.yc.tree.RedBlackTree;public class RedBlackTreeTest {public static void main(String[] args) {RedBlackTree<Integer> tree = new RedBlackTree<Integer>(1);tree.add(2);tree.add(3);tree.add(4);tree.add(5);tree.add(6);tree.add(7);tree.add(8);System.out.println( tree.breadthFirstSearch());tree.remove(6);System.out.println( tree.breadthFirstSearch());}}


测试结果为:

[[data:4, color:true], [data:2, color:false], [data:6, color:false], [data:1, color:true], [data:3, color:true], [data:5, color:true], [data:7, color:true], [data:8, color:false]][[data:4, color:true], [data:2, color:false], [data:7, color:false], [data:1, color:true], [data:3, color:true], [data:5, color:true], [data:8, color:true]]

参考书籍:《疯狂Java程序员的基本修养》

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 小孩读英语绘本大人不会翻译怎么办 东西掉到手刹缝里面了怎么办 泰拉瑞亚猩红之地蔓延到家里怎么办 三星r.467玩dnf闪退怎么办 美团外卖不小心撞到汽车怎么办 如果你在战场上遇到华裔美军怎么办 衣服洗完了干了后发黄怎么办 毛衣起球怎么办学会这几个小妙招 小车没电了打不着火怎么办 老婆花钱大手大脚又要我给钱怎么办 住酒店手机id被劫持了怎么办 孩子老是送玩具给别的小朋友怎么办 老板总想和我谈人生怎么办gl 导师让用师姐的数据写论文怎么办 签了平面模特协议想违约怎么办 福州96年以前社保手册丢了怎么办 被老师缴的手机弄没了怎么办 户口在成都医保在德阳生孩子怎么办 微课掌上通看不到孩子班级圈怎么办 微课掌上通的录音错误是怎么办 微课视频录制ppt里面音乐怎么办 老师在街上和别人吵起来了怎么办 手机录屏传到爱剪辑变成竖屏怎么办 尔雅通识课程考试忘记做了怎么办 尔雅通识课过了课程完成时间怎么办 微信账号没冻结登录不了怎么办 微信解封电话号码没有电话号怎么办 ps中智能对象不能直接编辑怎么办 工资低奖金高银行流水不够怎么办 我水费交了自来水公司不给开怎么办 艺考文化分数差4分二本怎么办 学信网手机号换了密码忘记了怎么办 自考毕业证学信网上查不到怎么办 苹果手机自带浏览器证书过期怎么办 手机连接工行证书介质失贩怎么办 老公要带与小三生的孩子回家怎么办 小三怀孕打胎后还是很爱老公怎么办 小三怀孕开不到引产证明怎么办 如何处理老公和小三有了孩子怎么办 老公出轨回归后还和小三联系怎么办 丈夫出轨想挽回这段婚姻该怎么办