最近看到好多红黑树的东西,英文好的童鞋能够直接点击http://www.cs.princeton.edu/~rs/talks/LLRB/RedBlack.pdf这里查看我以前学习的材料,对理解下面讲的东西确定也有点帮助(可是不彻底同样),英文通常的同窗就直接看个人文采飞扬把哈哈。还有你们能够去coursera上学习一些国外比较好的资料。感受比国内一些学习网站作的好不少。html
前面一篇随笔写的binarysearchtree(http://www.cnblogs.com/robsann/p/7567596.html)说了有一个缺点就是不平衡,意思就是插入已经排好序的对象的时候会变成一条链表,链表固然比二叉树要慢不少啦,随机插入的话二叉树的各类方法须要的时间和LgN的成正比。因此红黑树实际上是在解决二叉树不平衡的问题的。java
2 - 3 - 4 Tree 我以为算是一种模型,一种树模型保证了树是平衡的,所谓平衡就是树不会一个枝头长得很高,另一个枝头长得很矮,那保证平衡有什么用?平衡的状况下,全部操做须要的时间都是和LgN成正比的,你说腻害不腻害。node
2 - 3 - 4 树,容许一个节点是 2-nodes 或者是 3 nodes 或者是 4 nodes, 具体的意思就是说git
这是一个2-nodes, 有2个触手,可以指向不一样的2个子元素,左边的子元素小于A,右边的子元素大于Agithub
3-nodes, 3 分叉,最左边的子元素小于C,中间的子元素between c和e, 最右边的子元素大于Eide
4-nodes, 3分叉。同上学习
看看2-3-4tree是如何保证平衡的
网站
假设有 10 7 6 3 8 11 15 要插入 2 -3 4 树中ui
这里插入还有另一种选择叫Bottom-up solution,之下而上的一种解法(能够忽略)。就是先找到这个节点会被插入的位置,若是插入后变成了5-nodes,就把其中一个节点往父节点抛出,让父节点和其中的一个子节点结合。this
删除同理,为了要保证树的平衡,当删除的时候,若是被删除的节点只有一个元素的话,必需要把父亲元素拉下来造成一个3-nodes而后删除后变成2-nodes(后面还会说)
红黑树和2-3-4树的关系,红黑树是2-3-4树的一种实现方式。2-3-4树只是一个模型。
先介绍一下节点对象Node,看代码,以后会用到这个对象
private class Node { private K k; // 这个节点的key private V v; //节点的value private Node left, right; //左右节点 private int size; //节点为根的树的大小 private boolean color; //节点的颜色,分黑和红 Node(K k, V v, int size, boolean color) { this.k = k; this.v = v; this.size = size; this.color = color; } @Override public String toString() { if (color) return "red " + v; else return "black " + v; } }
左倾红黑树和红黑树的区别以下
这是通常的红黑树,3-node 的时候红节点能够左倾或者右倾
左边是2-3-4树的模型,右边是红黑树的实现
这是左倾红黑树, 3-nodes的时候红色的节点必须在左边。
用常量来表示红黑
首先左倾红黑树的性质必需要获得保证(就是上图中说的Require that 3 -node be left leaning),可是不少时候可能由于插入或者删除的操做破坏了这个性质,因此咱们必需要修正。
这里介绍3个修正的方法
第一个方法的使用场景是这样子的,(为了避免破坏平衡,每次插入的都是红节点),下图插入的节点在右边,破坏了左倾的性质,因此必须rotateLeft。rotateLeft是指把红色节点左移
方法以下
//右树是红link的时候,turn this red link to left private Node rotateLeft(Node h) { assert(isRed(h.right)); Node x = h.right; //change the pointers h.right = x.left; x.left = h; x.color = h.color; //change the colors h.color = RED; x.size = h.size; //change the sizes h.size = size(h.left) + size(h.right) + 1; return x; }
另一种状况是最新插入的节点在最左边,把中间节点rotateRight,从新平衡
方法以下
//左树是红link的时候,turn this red link to left private Node rotateRight(Node h) { assert(isRed(h.left)); Node x = h.left; h.left = x.right; x.right = h; x.color = h.color; h.color = RED; x.size = h.size; //size is the same h.size = size(h.left) + size(h.right) + 1; return x; }
这张图是破坏了性质以后,修正的办法
还有一个方法是flipColors(),代码以下,就是可能插入的时候须要知足当前节点不是4-nodes,可能就会使用这个方法
有了上面这些辅助的方法后就能够开始下面的学习了
在2-3-4tree中的介绍中也知道了,put的时候,当前节点若是是4-nodes的话就没有位置留给须要插入的对象了。
因此咱们在put的时候,必定要保证当前的节点(currentNode)之后用cn来表示。cn必须不是4-nodes, 若是是4-nodes的话就用flipColor把4-node变成3个2-node
假设咱们要插入10 7 6 3 这4个对象, 大片动态图,燃烧的经费。
附上代码
public void put(K k, V v) { root = put(root, k, v); root.color = BLACK; } private Node put(Node cn, K k, V v) { if (cn == null) return new Node(k, v, 1, RED); if(isRed(cn.left) && isRed(cn.right)) split4Node(cn);//是4节点的话 就split int cmp = k.compareTo(cn.k); if (cmp > 0) cn.right = put(cn.right, k, v); // k > node.k go right else if (cmp < 0) cn.left = put(cn.left, k, v); else cn.v = v; //hit //following code is to fix the tree on the way up if (isRed(cn.right) && !isRed(cn.left)) cn = rotateLeft(cn); // right leaning 3nodes的时候 须要变成 left leaning if (isRed(cn.left) && isRed(cn.left.left)) cn = rotateRight(cn); //变成了一个4节点 cn.size = size(cn.left) + size(cn.right) + 1; return cn; }
树的get方法其实很简单,就是判断key是否是相等,若是相等就return 这个值。
public V get(K k) { return get(root, k); } //cn means currentNode private V get(Node cn, K k) { if (cn == null) return null; // not find the key int cmp = k.compareTo(cn.k); if(cmp > 0) return get(cn.right, k); else if (cmp < 0) return get(cn.left, k); else return cn.v; // hit }
删除能够说是最难的了把,基本的思想就是,cn节点(当前节点)不会是2-node,(若是root节点是2-nodes,咱们须要把root节点变成红节点)
保证其中一个子节点不是2节点(这个保证须要看删除的节点位于当前节点的哪里,好比删除的节点比cn节点小,因此接下来咱们会往left走,因此要保证left节点不是2-node)。由于2节点若是删除了的话就不会平衡。因此必需要把红节点从root一步一步carry下去。
先实现一个deleteMin方法,咱们要把红节点带向左边。想想,红节点带向左边后,若是左边有节点删除了可能没办法保持平衡,红节点能够变成黑节点,代替刚才被删除的节点。经过这样子能够保证左边的树是必定会平衡的。
deleteMin的几种状况
1. , 如今能够直接删除掉。也不会影响平衡。删除了后3节点变成了2节点。
2., 须要继续向左走,可是左子节点是2-node,必需要想办法变成不是2-node。因此须要把父亲节点和兄弟节点和cn节点。整合在一块儿变成4-node
3. , 须要继续向左走,可是左子节点是2-node,必需要想办法变成不是2-node。发现兄弟节点是否是2-nodes。因此把兄弟节点借一个node过来
2和3这2种状况总结在一块儿就是moveRedLeft的代码
public void deleteMin() { //保证了root节点不是2nodes if (!isRed(root.left) && !isRed(root.right)) root.color = RED; root = deleteMin(root); root.color = BLACK; } public Node deleteMin(Node cn) { if (cn.left == null) return null; if (!isRed(cn.left) && !isRed(cn.left.left)) //判断左边子节点是否是2node,是的话就须要把Red带下去 cn = moveRedLeft(cn); cn.left = deleteMin(cn.left); return fixup(cn); } private Node fixup(Node h) { if (isRed(h.right) && !isRed(h.left)) h = rotateLeft(h); //右倾 if (isRed(h.left) && isRed(h.left.left)) h = rotateRight(h); h.size = size(h.left) + size(h.right) + 1; //right the size return h; }
随意的delete方法。按照下面的图说一下,基本的思想。
首先删除D。从H开始,H不是2-node(右边有一个红节点),D小于H,左往H的左边找
找到了D。D不是2-node且是红节点。找到了D,处理办法是找到右树中最小的值,发现是E, 把最小的值赋值给当前的node
因此咱们要往右边走。可是发现右边节点F是2-node。因此咱们要把红色的链接往右边带。
flipColor(D), B D 和 F就变成了一个4-node(能够本身作一下图看看是否是这样子的)。 这时候红连接也往右边带了。
如今到了F 节点。发现F 的左节点是 2-node。把红色的连接往左边带。
flipColor(F),这个时候F 和 E 和 G ,变成了一个4-node。 瓜熟蒂落删除左边的节点。没有影响平衡。
接着原路返回,修复节点。
public void delete(K k) { if (k == null) throw new IllegalArgumentException("argument to delete() is null"); if (!contains(k)) return; if (!isRed(root.left) && !isRed(root.right)) root.color = RED; root = delete(root, k); if (root != null) root.color = BLACK; } public boolean contains(K k) { return get(k) != null; } private Node delete(Node cn, K k) { if (cn == null) return null; int cmp = k.compareTo(cn.k); if (cmp < 0) { // k < node.k go left if (!isRed(cn.left) && !isRed(cn.left.left)) //保证了下一个左元素不是2nodes cn = moveRedLeft(cn); cn.left = delete(cn.left, k); } else if (cmp > 0) { // k > node.k go right if (isRed(cn.left) && !isRed(cn.right)) //若是是3节点的话须要 rotate 把red转到右边 cn = rotateRight(cn); if (!isRed(cn.right) && !isRed(cn.right.left)) //保证下一个右节点不是2nodes cn = moveRedRight(cn); cn.right = delete(cn.right, k); } else { //hit if (isRed(cn.left) && !isRed(cn.right)) cn = rotateRight(cn); if (k.compareTo(cn.k) == 0 && (cn.right == null)) //find null just return null return null; if (!isRed(cn.right) && !isRed(cn.right.left)) //保证下一个右节点不是2nodes cn = moveRedRight(cn); if (k.compareTo(cn.k) == 0) { Node x = min(cn.right); cn.k = x.k; cn.v = x.v; cn.right = deleteMin(cn.right); } else cn.right = delete(cn.right, k); } return fixup(cn); }
具体的实现能够参考一下https://github.com/Cheemion/algorithms/blob/master/src/com/algorithms/tree/LeftLeaningRedBlackTree.java
可能有地方说的不清楚哈。见谅,能够留言我有不清楚的地方