《算法导论》中对于红黑树的定义如下:
1.每个结点或是红的,或是黑的
2.根节点是黑的
3.每个叶节点是黑的(隐藏的空的叶子节点)
4.如果一个结点是红的,则它的两个儿子都是黑的
5.对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点
红黑树是平衡的树,插入新节点默认为红色,若为根节点,变黑色。
关于插入一个新节点:
- 如果它的parent是黑色,则直接插入不需要调整
- 如果parent是红色,有两种情况
- 如果parent的兄弟结点为空,要旋转+变色,左旋或右旋与插到哪边有关。
- parent兄弟结点不空且为红色,则parent和其兄弟节点变黑色,祖父结点变红色
- parent兄弟节点不空为黑色,旋转+变色,都是优先考虑祖孙三代,然后递归到根节点。
AVL是严格平衡树,因此在增加或者删除节点的时候,根据不同情况,旋转的次数比黑红树要多,所以红黑树的插入效率更高。红黑树不追求完全平衡,AVL完全平衡。
Jdk8HashMap实现:数组+链表+红黑树
1.7版本的hashMap采用的是数组加链表方式实现的,无论如何,当链表的长度过长时,get元素的遍历时间就会增长,为了解决这一问题,1.8版本的hashMap加入了红黑树。
1.7版本中使用很多的右移和异或运算提高hashMap的散列性,1.8版本采用红黑树来提高散列性,就没有使用太多的右移和异或。
当链表size大于8时,会将其改为红黑树(提高插入和查询效率),然后将元素放到红黑树里。调用put时,有一个节点数组层,存放首节点,可以判断节点类型,链表还是树,超长链表转化成红黑树。插入时,判断当前节点是否是尾结点,然后new一个Node放到尾结点,由此可见1.8版本是采用尾插法。这和红黑树有关,是否转成红黑树,要先判断当前链表有多长,就需要遍历到尾结点,而如果采用头插法,还需要将整个链表向下移一位。采用尾插法的另一个好处是,扩容的时候不会出现1.7里面的扩容后反序的情况,也就不会出现死循环列表。
树化方法细节:首先判断数组长度,如果数组为空,会初始化数组;如果长度小于64会进行扩容。然后判断数组inde位置上的元素是否为空,不为空就进行树化,遍历链表的每一个元素,new一个TreeNode,将元素属性传给TreeNode。
将单向链表转成双向链表,这样就利于再转化成红黑树。
红黑树新节点插入时:
判断parent是否为空,若为空则是根节点,red=false,直接返回,就插入了一个根节点。
扩容:
1.7的hashMap扩容除了判断数组大小是否大于threshold,还判断数组index位置的元素是否为空,两个条件都满足才扩容。
1.8的hashMap只判断数组大小是否大于threshold。
1.7的转移元素是一个一个遍历去转移