之前一篇讲关于二叉搜索树的文章(参看:点击打开链接),但是存在一个问题,在最坏情况下,插入的节点本身就是有序的,要么是从大到小排列,要么是从小到大排列,那最后得到的排序二叉树将会变成链表。这样其检索效率特别差。
如图:
为了改进这种不足,Rudolf Bayer于1972年发明了另一种改进后的排序二叉树,他将这种树称为“对称二叉树”,红黑树后来被人提出命名。
在二叉树的基础上增加了这些要求:
1.每个节点要么红色要么黑色 --- 增加一个颜色属性
2.根节点永远是黑色的。
3.叶子节点(NIL节点)黑色的 。
4.每个红色节点的两个子节点都是黑节点(从每个叶子到根的路径上不会有两个连续的红色节点)。
5.从任一节点到其子树中每个节点的路径都包含相同数量的黑色节点。
注意:1.在性质三中,指定红黑树的叶子节点是NIL节点而且叶子节点都是黑色。但是Java实现红黑树时将使用NIL来代替空节点,因此遍历红黑树的时候将看不到全是黑色的叶子节点,反而会有红色的出现。
2.根据性质五,我们可以将从根节点到叶子节点的路径中包含的黑色节点数称为“黑色高度”。
3.根据性质四,保证了从根节点到叶子节点的最长路径的长度不会超过任何其他路径的两倍。最长的路径就是一条红黑交替的路径。
由此有这样的结论:对于给定黑色高度为N的红黑树,从根节点到叶子节点的最短路径长度为N- 1,最长为2*(N- 1)。(有了这种限制来确保它大致是平衡的-因为红黑树的高度不会无限增加)。
节点类:
private static boolean RED = false; private static boolean BLACK = true; public class Node{ //依次保存左右节点以及父节点 Node left; Node right; Node parent; //存放数据值 private Object data; 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(){//只返回data的值 return data + "" + color +""; } public boolean equals(Object obj){//判断两个对象是否相等 if(this == obj){ return true; } if(obj.getClass() == Node.class){ //getClass()进行类型判断 instanceof: 判断左边对象是否是右边类的实例 --boolean类型的值 Node target = (Node)obj; return data.equals(target.data) && color == target.color && left == target.left && right == target.right && parent == target.parent; } return false; } }
红黑树和二叉排序树的基本功能相似,只是在添加了诸多要求后,插入和删除存在不同。
后面我们会详细介绍它的插入和删除节点。但是根据前面的诸多性质和要求,当进行插入和删除之后可能会导致它不在符合红黑树的性质。因此我们需要进行维护。而维护时需要进行一些特殊的转换。所以这里先介绍一些功能函数。
最重要的就是旋转,分为左旋和右旋。我们在这里使用的是中序遍历该二叉树,所以旋转的原则就是不改变它的基本输出顺序。
如图:
参考代码(包括一些其他的函数,包括遍历函数):
//右旋 private void rotateRight(Node node) { if(node != null){ //获取左节点 Node leftNode = node.left; Node q = leftNode.left; //将leftNode的右节点链接到node节点的左节点链上 node.left = q; //让leftNode的右节点的parent指向node节点 if(q != null){ q.parent = node; } leftNode.parent = node.parent; //如果node是根节点 if(node.parent == null){ root = leftNode; } //如果node是其父节点的右子节点 else if(node.parent.right == node){ //将rightNode设为node的父节点的左节点 node.parent.right = leftNode; } else{ node.parent.left = leftNode; } leftNode.right = node; node.parent = leftNode; } } //左旋 private void rotateLeft(Node node) { if(node != null){ //获取右节点 Node rightNode = node.right; Node q = rightNode.left; //将rightNode的左节点链接到node节点的右节点链上 node.right = q; //让rightNode的左节点的parent指向node节点 if(q != null){ q.parent = node; } rightNode.parent = node.parent; //如果node是根节点 if(node.parent == null){ root = rightNode; } //如果node是其父节点的左节点 else if(node.parent.left == node){ //将rightNode设为node的父节点的左节点 node.parent.left = rightNode; } else{ node.parent.right = rightNode; } rightNode.left = node; node.parent = rightNode; } } // 为定点设置颜色 private void setColor(Node y, boolean color) { if(y != null){ y.color = color; } } //获得指定节点的颜色 private boolean colorOf(Node y) { return (y == null ? BLACK : y.color); } //获取指定节点的父节点 private Node parentOf(Node y) { return (y == null ? null : y.parent); } //获取指定节点的右节点 private Node rightOf(Node y) { return (y == null ? null : y.right); } //获取指定节点的左节点 private Node leftOf(Node y) { return (y == null ? null : y.left); } //广度优先遍历 public List<Node> breadFirst(Node node){ Queue<Node> queue = new ArrayDeque<Node>(); List<Node> list = new ArrayList<Node>(); if(root != null){ queue.offer(root); } while(!queue.isEmpty()){ //将队列的“队尾”元素添加到List中去 list.add(queue.peek()); Node p = queue.poll(); if(p.left != null){ queue.offer(p.left); } if(p.right != null){ queue.offer(p.right); } } return list; } //中序遍历 public List<Node> inIterator(){ return inIterator(root); } public List<Node> inIterator(Node node) { List<Node> list = new ArrayList<Node>(); if(node.left != null){ list.addAll(inIterator(node.left)); } list.add(node); if(node.right != null){ list.addAll(inIterator(node.right)); } return list; }
以上就是这篇的主要内容,图片制作粗糙,请君见谅。若是发现有错误之处或者又可以改进的地方,请指出,谢谢。