查找(二):彻底理解红黑树和平衡查找树
平衡查找树
在之前的二分搜索和二叉查找树中已经能够很好地解决查找的问题了,但是它们在最坏情况下的性能还是很糟糕,我们可以在查找二叉树中,每次动态插入或删除某结点时,都重新构造为完全二叉树,但是这样代价太大,所以就引出了平衡查找树。
详细的数学定义就不给出了,因为既不直观也记不住,直接给出一个平衡二叉树的图:
相信这个图一看就明白了,平衡查找树(以下简称BST或2-3查找树),下面给出一些基本的定义:
一棵2-3查找树或为一棵空树,或由一下结点组成:
2-结点,含有一个键(及其对应的值)和两条链接,左链接指向的2-3树中的键都小于该结点,右链接指向的2-3树中的键都大于该结点。
3-结点,含有两个键(及其对应的值)和三条链接,左链接指向的2-3树中的键都小于该结点,中链接指向的2-3树中的键都位于该结点的两个键之间,右链接指向的2-3树中的键都大于该结点。
因为平衡二叉树在插入和删除过程中需要判断插入的结点时2-结点还是3-结点等等一系列问题,实现起来代码量特别大,并且会增加额外开销,所以就提出了红黑树。
红黑树
红黑树的基本思想是用标准的二叉查找树(完全由2-结点构成)和一些额外的信息(替换3-结点)来表示2-3树。
先给图:
由上图可以很明显的看到红黑树可以完全代替2-3树,下面给出红黑树的定义:
- 红连接均为左链接。
- 没有任何一个结点同时和两条红链接相连
- 该树是完美黑色平衡的,即任意空链接到根节点的路径上的黑链接的数量相同
先给出红黑树Node的定义:
private class Node<T> { T key; Node leftChild = null; Node rightChild = null; boolean color; Node(T key,boolean color) { this.key = key; this.color = color; } }
在Node类中,相比普通的二叉树,只是多了一个color属性,因为对于每一个非根节点,有且仅有一个链接指向它,所以这个color用来表示指向这个节点的链接是红色还是黑色,也可以理解为此节点是红色或黑色。
然后再给出两个操作:左旋转和右旋转以及颜色转换。
这三个操作都是局部改变,并不会对整个红黑树造成破坏
左旋转:
对于这个情况,h的右链接为红色,不符合红黑树的定义
操作的代码如下:
public <T> Node<T> rotateLeft(Node<T> h) { Node<T> x = h.rightChild; h.rightChild = x.leftChild; x.leftChild = h; x.color = h.color; h.color = RED; return x; }
同理右旋转也就是将左边的红色链接换到右边:
public <T> Node<T> rotateRight(Node<T> h) { Node<T> x = h.leftChild; h.leftChild = x.rightChild; x.rightChild = h; x.color = h.color; h.color = RED; return x; }
颜色转换:
实现代码
public <T> void flipColors(Node<T> h) { h.color = RED; h.leftChild.color = BLACK; h.rightChild.color = BLACK; }
这三种操作说完了,那么我们为什么需要这三个操作呢? 其实数学原理特别复杂,如果大家有兴趣可自行GOOGLE。
答案是这三个操作时用来构建一个红黑树的,当插入一个元素到红黑树中时,需要沿着插入点到根节点的路径上向上移动,对于经过的每个结点,有如下操作:
- 如果右子节点是红色的而左子节点也是红色的,进行右旋转。
- 如果左子节点是红色的且它的左子节点也是红色的,进行右旋转
- 如果左右子节点均为红色,进行颜色转换。
这样的话只需要在二叉查找树上稍作修改即可,完整代码如下:
public class RedBlackTree extends SearchBase { private static final boolean RED = true; private static final boolean BLACK = false; @SuppressWarnings("unused") private class Node<T> { T key; Node leftChild = null; Node rightChild = null; boolean color; Node(T key,boolean color) { this.key = key; this.color = color; } } /* (non-Javadoc) * @see Search.SearchBase#search(java.lang.Comparable) */ @Override public <T> Integer search(Comparable<T> key) { // TODO Auto-generated method stub return null; } @SuppressWarnings("unchecked") public <T> T search(Comparable<T> key,Node<T> root) { // TODO Auto-generated method stub Node<T> node = root; while(node != null) { if(key.compareTo((T) node.key) < 0) { node = node.leftChild; } else if(key.compareTo((T) node.key) > 0){ node = node.rightChild; } else { break; } } if(node == null) return null; else return (T) node.key; } public <T> Node<T> rotateLeft(Node<T> h) { Node<T> x = h.rightChild; h.rightChild = x.leftChild; x.leftChild = h; x.color = h.color; h.color = RED; return x; } public <T> Node<T> rotateRight(Node<T> h) { Node<T> x = h.leftChild; h.leftChild = x.rightChild; x.rightChild = h; x.color = h.color; h.color = RED; return x; } public <T> void flipColors(Node<T> h) { h.color = RED; h.leftChild.color = BLACK; h.rightChild.color = BLACK; } public <T> boolean isRed(Node<T> node) { if(node == null) return false; return node.color == RED; } @SuppressWarnings("unchecked") public <T> Node<T> put(Node<T> node,Comparable<T> key) { if(node == null) return new Node(key,RED); if(key.compareTo((T) node.key) < 0) node.leftChild = put(node.leftChild,key); else if(key.compareTo((T) node.key) > 0) node.rightChild = put(node.rightChild,key); else node.key = (T) key; if(isRed(node.rightChild) && !isRed(node.leftChild)) node = rotateLeft(node); if(isRed(node.leftChild) && isRed(node.leftChild.leftChild)) node = rotateRight(node); if(isRed(node) && isRed(node.rightChild)) flipColors(node); return node; } public <T> void traverseTree(Node<T> node) { if(node == null) return; traverseTree(node.leftChild); System.out.print(node.key); traverseTree(node.rightChild); } public static <T> void main(String[] args) { Integer[] b = {1,4,2,6,7,0,3}; RedBlackTree redBlackTree = new RedBlackTree(); RedBlackTree.Node<Integer> root = null; for(int i=0;i<b.length;i++) { root = redBlackTree.put(root, b[i]); } redBlackTree.traverseTree(root); System.out.println(); Integer key = redBlackTree.search(8, root); System.out.println(key); } }
红黑树平均插入效率:lgN
红黑树平均查询效率:lgN
作者