红黑树是一种自平衡二叉树,红黑树和AVL树同样都对插入时间、删除时间和查找时间提供了最好可能的最坏状况担保。算法
红黑树须要知足的五条性质: this
性质一:节点是红色或者是黑色; spa
在树里面的节点不是红色的就是黑色的,没有其余颜色,要不怎么叫红黑树呢,是吧。 .net
性质二:根节点是黑色; blog
根节点老是黑色的。它不能为红。 递归
性质三:每一个叶节点(NIL或空节点)是黑色; 图片
实现的时候NIL节点是个空节点用null表示,而且是黑色的ip
性质四:每一个红色节点的两个子节点都是黑色的(也就是说不存在两个连续的红色节点); rem
连续的两个节点不能是连续的红色,连续的两个节点的意思就是父节点与子节点不能是连续的红色get
性质五:从任一节点到其每一个叶子节点的全部路径都包含相同数目的黑色节点;
从根节点到每个NIL节点的路径中,都包含了相同数量的黑色节点。
这五条性质就约束了红黑树能够将查找删除维持在对数时间内,算法导论中有证实一颗有n个节点的红黑树的高度至多为2lg(n+1)
红黑树相对于普通平衡二叉树一个很重要的操做就是旋转
假设 : 旋转节点为n ,n的左子树叫作leftChild,n的右子树叫作rightChild
一、向左旋转
rightChild的左子树做为n的右子树,
将n的右节点做为跟,
最后将n做为rightChild的左节点;
二、向右旋转
leftChild的右子树做为n节点左子树,
将n的左节点做为跟,
最后将n做为leftChild的左节点
由于要知足红黑树的这五条性质,若是咱们插入的是黑色节点,那就违反了性质五,须要进行大规模调整,若是咱们插入的是红色节点,那就只有在要插入节点的父节点也是红色的时候违反性质四或者是当插入的节点是根节点时,违反性质二,因此,咱们应该把要插入的节点的颜色变成红色。
1.叔叔节点存在且为红色,这时只须要将父,叔节点置为黑色,将祖父节点(祖父节点一定存在)置为红色,而后以祖父节点递归上面的调整过程
insertFixup(grandParent), 你能够想像成祖父节点为新插入的节点便可;
2.叔节点为黑色或不存在,就须要进行旋转调整,这里讲述一种状况,其余类同:
假如咱们对上面的初始图添加一个节点5
明显如今树不知足性质4,因此须要进行旋转,并且经过一次旋转是不够的,咱们须要进行两次旋转:
第一次旋转:以新添加的节点5的父节点6为根进行右旋转后:
第二次旋转,先调整颜色,将5置为黑色,1,6置为红色,以节点1进行左旋转:
首先你要了解普通二叉树的删除操做:
1.若是删除的是叶节点,能够直接删除;
2.若是被删除的元素有一个子节点,能够将子节点直接移到被删除元素的位置;
3.若是有两个子节点,这时候就能够把被删除元素的右支的最小节点(被删除元素右支的最左边的节点)和被删除元素互换,咱们把被删除元素右支的最左边的节点称之为后继节点(后继元素),而后在根据状况1或者状况2进行操做。
对于删除操做,咱们须要考虑几种状况(来自维基百科):
/** * @author lpf * @create 2018-03-22 20:32 * * 红黑树实现: * 性质: * 1.节点要么红,要么黑; * 2.根是黑色; * 3.全部叶子都是黑色;(叶子为null节点) * 4.每一个红色节点的两个子节点都是黑色(从每一个叶子到根的全部路径上不能有两个连续的红色节点) * 5.从任一节点到其每一个叶子的全部简单路径都包含相同数目的黑色节点 * **/ public class RedBlackTree<T extends Comparable<T>> { private Node<T> root; // 树的跟节点 private int size; // 树元素个数 //标志叶子节点表示空节点,颜色为黑色 private Node<T> NIL = new Node<T>(null, null, null, null, Color.BLACK); /** * 节点类 */ private static class Node<E>{ E value; Node<E> parent; Node<E> left; Node<E> right; Color color; public Node(E value, Node<E> parent, Node<E> left, Node<E> right, Color color) { this.value = value; this.parent = parent; this.left = left; this.right = right; this.color = color; } } /** * 节点颜色 */ private static enum Color{ RED, BLACK } /** * 获取叔叔节点 * @param n 当前节点 * @return 其叔节点 */ private Node<T> uncle(Node<T> n){ Node<T> gp = grandParent(n); if (gp == null){ return null; } if (n.parent == gp.left){ //若其父节点在其祖父节点左边 return gp.right; } else { return gp.left; } } /** * 获取祖父节点 * @param n 当前节点 * @return 其祖父节点 */ private Node<T> grandParent(Node<T> n){ if (n.parent == null) return null; return n.parent.parent; } /** * 返回最小元素 * @return 获取某节点为根的树的最小元素 */ public T min(Node<T> n) { Node<T> min = minN(n); return min == NIL ? null : min.value; } /** * 返回最小节点 * @param n 树根节点 * @return 最小节点 */ private Node<T> minN(Node<T> n) { Node<T> min = n; while (min.left != NIL) { min = min.left; } return min == NIL ? null : min; } /** * 获取某节点为根的树的最大元素 * @return 最大元素, 没有返回null */ public T max(Node<T> n) { Node<T> max = maxN(n); return max == NIL ? null : max.value; } /** * 获取某节点为根的树的最大节点 * @return 最大节点, 没有返回null */ public Node<T> maxN(Node<T> n) { Node<T> max = n; while (max.right != NIL) { max = max.right; } return max == NIL ? null : max; } /** * 左旋以n节点为根的子树: * 1.将rightChild的左子树做为n的右子树 * 2.将rightChild做为根 * 3.将n节点做为rightChild的左孩子 */ private void leftRotate(Node<T> n){ Node<T> rightChild = n.right; //1.将rightChild的左子树做为n的右子树 //将rightChild的左子树接到n的右边 n.right = rightChild.left; if(rightChild.left != NIL) rightChild.left.parent = n; //2.将rightChild做为根 rightChild.parent = n.parent; if (n.parent == null){ //若n为树根 root = rightChild; } else if (n.parent.left == n){ //若n为父亲的左孩子 n.parent.left = rightChild; } else { //若n为父亲的右孩子 n.parent.right = rightChild; } //3.将n节点做为rightChild的左孩子 rightChild.left = n; n.parent = rightChild; } /** * 右旋以n节点为根的子树: * 1.将leftChild的右子树做为n的左子树 * 2.将leftChild做为根 * 3.将n节点做为leftChild的右孩子 */ private void rightRotate(Node<T> n){ Node<T> leftChild = n.left; //1.将leftChild的右子树做为n的左子树 n.left = leftChild.right; if (leftChild.right != NIL){ leftChild.right.parent = n; } //2.将leftChild做为根 leftChild.parent = n.parent; if (n.parent == null){ //n为树根 root = leftChild; } else if (n == n.parent.left){ //n为父节点点左孩子 n.parent.left = leftChild; } else{ //n为父节点右孩子 n.parent.right = leftChild; } //3.将n节点做为leftChild的右孩子 leftChild.right = n; n.parent = leftChild; } /** * 调整树以知足红黑树性质 * @param n 新添加的节点 */ private void insertFixup(Node<T> n) { //如果树根 if (n.parent == null){ n.color = Color.BLACK; return; } //父节点为黑色,无须调整 if (n.parent.color == Color.BLACK){ return; } Node<T> u = uncle(n); Node<T> g = grandParent(n); // 1.父节点及叔节点都为红色 if (u != null && u.color == Color.RED){ //将parent和uncle颜色置BLACK n.parent.color = Color.BLACK; u.color = Color.BLACK; //将grand parent置RED g.color = Color.RED; //递归调整 grand parent, 这时可想像grand parent为新添加的红色节点 insertFixup(g); } else { //父节点P是红色而叔节点是黑色或缺乏 if (n == n.parent.right && n.parent == g.left){ //n为父节点右孩子,且父节点为祖父节点的左孩子 //以父左旋 leftRotate(n.parent); n = n.left; } else if(n == n.parent.left && n.parent == g.right){ //n为父节点左孩子,且父节点为祖父节点右孩子 //以父右旋 rightRotate(n.parent); n = n.right; } n.parent.color = Color.BLACK; //parent颜色置为黑色 g.color = Color.RED; if (n == n.parent.left && n.parent == g.left){ //n节点为父节点的左孩子,且父节点为祖父节点的左孩子 //以祖父右旋 rightRotate(g); } else{ //n节点为父节点的右孩子,且父节点为祖父节点的右孩子 //以祖父左旋 leftRotate(g); } } } /** * 删除元素 * 相似二叉查找树的删除 * @param t 待删除节点 * @return 删除成功返回true, 反之返回false */ public boolean remove(T t) { boolean removed = false; Node<T> n = getN(t); // 获取要删除的节点 Node<T> replace = null; // 用于替换节点n Node<T> child = null; // 后继节点next的孩子节点 if (n != null) { if (n.left == NIL || n.right == NIL) { // 如有最多一个非空孩子 replace = n; } else { // 如有2个非空孩子, 则找其后继节点 replace = locateNextN(n); } // 获取替换节点replace的孩子, 有可能为NIL child = replace.left != NIL ? replace.left : replace.right; // 删除节点replace, 链接replace父节点-->child节点 child.parent = replace.parent; if (replace.parent == null) { // 根节点 root = child; } else if (replace == replace.parent.left) { // replace为其父节点左孩子 replace.parent.left = child; } else { // replace为其父节点右孩子 replace.parent.right = child; } // 替换n节点的值为replace节点 if (replace != n) { n.value = replace.value; } // 若后继节点为黑色, 则需作调整, 由于删除红色replace节点对红黑树性质无影响 if (replace.color == Color.BLACK) { removeFixup(child); } removed = true; } return removed; } /** * 因为删除节点而作调整 * @param n 删除节点的后继节点的孩子 */ private void removeFixup(Node<T> n) { while (n != root && n.color == Color.BLACK) { if (n == n.parent.left) { // n为其父节点的左孩子 Node<T> rightBrother = rightBrother(n); if (rightBrother.color == Color.RED) { // 兄弟颜色为红 rightBrother.color = Color.BLACK; n.parent.color = Color.RED; leftRotate(n.parent); // 以父左旋 rightBrother = n.parent.right; } // 右兄弟的两个孩子都为黑色 if (rightBrother.left.color == Color.BLACK && rightBrother.right.color == Color.BLACK) { rightBrother.color = Color.RED; n = n.parent; } else if (rightBrother.right.color == Color.BLACK) { // 右兄弟右孩子为黑色 rightBrother.left.color = Color.BLACK; rightBrother.color = Color.RED; rightRotate(rightBrother); rightBrother = n.parent.right; } else { // 右兄弟右孩子为红色或其余状况,好比为空叶子节点NIL rightBrother.color = n.parent.color; n.parent.color = Color.BLACK; rightBrother.right.color = Color.BLACK; leftRotate(n.parent); n = root; } } else { // n为其父节点的右孩子 Node<T> leftBrother = leftBrother(n); if (leftBrother.color == Color.RED) { // 左兄弟为红色 leftBrother.color = Color.BLACK; n.parent.color = Color.RED; rightRotate(n.parent); leftBrother = n.parent.left; } if (leftBrother.left.color == Color.BLACK && leftBrother.right.color == Color.BLACK) { // 左兄弟左孩子和右孩子都为黑色 leftBrother.color = Color.RED; n = n.parent; } else if (leftBrother.left.color == Color.BLACK) { // 仅左兄弟左孩子为黑色 leftBrother.color = Color.RED; leftBrother.right.color = Color.BLACK; leftRotate(leftBrother); leftBrother = n.parent.left; } else { // 左兄弟左孩子为红色 leftBrother.color = n.parent.color; n.parent.color = Color.BLACK; leftBrother.left.color = Color.BLACK; rightRotate(n.parent); n = root; } } } n.color = Color.BLACK; } /** * 获取节点的右兄弟 * @param n 当前节点 * @return 节点的右兄弟 */ private Node<T> rightBrother(Node<T> n) { return n == null ? null : (n.parent == null ? null : n.parent.right); } /** * 获取节点的左兄弟 * @param n 当前节点 * @return 节点的右兄弟 */ private Node<T> leftBrother(Node<T> n) { return n == null ? null : (n.parent == null ? null : n.parent.left); } }
参考资料
算法导论第十三章