源码分析——HashMap(二)

红黑树


本篇主要介绍红黑树的基本概念以及相关的基本操作,并且讲解在HashMap中的主要实现。

首先,红黑树是一种自平衡二叉查找树,它由AVL树与2-3树演化而来,它与AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的的查找性能,虽然操作比较复杂,但是它的最坏情况运行时间也是非常良好的,并且在实践中是高效的:可以在O(log n)时间内做查找,删除与插入操作。这样说可能不大清楚,举个例子吧,假如有十亿数据,你如何在里面找到你想要的那一个?

最直接的办法,就是逐条对比,那么这样的操作平均要进行(n/2)次,即5亿次,而如果建立红黑树,将数据作为红黑树的结点,那么再进行查找则需要(log2(1000000000)[即log以2为底十亿的对数])= 29.89。即三十次左右,程序性能提高1600万倍以上。

那么红黑树是如何构建的呢?

首先,红黑树有五大性质:

1.树中结点要么黑色要么红色

2.根节点必须为黑色

3.每个叶节点,即空结点(NIL)为黑色

4.红结点的左右孩子结点必须为黑色(不可能存在两个连续的红色结点)

5.从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点

如下图,则是一颗典型的红黑树:

只有树中每一个结点都满足这五种特性,这棵树才算红黑树。

接下来看一下HashMap中的红黑树结点类TreeNode类是如何定义的:

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V>{
//静态内部类,继承的是LinkedHashMap中的Entry类
	TreeNode<K,V> parent;//指向父节点
	TreeNode<K,V> left;//指向左孩子
	TreeNode<K,V> right;//指向右孩子
	TreeNode<K,V> prev;//指向前置结点
	boolean red;//设置是红结点还是黑结点
			
	/*
	    此为继承下来的成员变量:
	    final int hash;
	    final K key;
	    V value;
	    Node<K,V> next;
	*/
}

由于我们上一篇中讲过java 1.8是使用红黑树+链表来解决哈希碰撞的,所以红黑树中的树结点同时也是双向链表中的结点,因此有前置指针prev和后置指针next,分别指向前一个结点和后一个结点,同时它还有树结点的特性,即父结点指针parent,左孩子指针left和右孩子指针right,分别指向父结点,左孩子和右孩子。另外三个成员变量用来存储key-value的hash值、key值与value值。那么除了这些常用成员变量之外,还有一个额外的变量red,这个变量就是红黑树的标志,用来标明此结点是红色的还是黑色的,采用Boolean型变量表示,true表示红色,false表示黑色。

基本的数据结构搞定了,那么如何建立一颗红黑树呢?首先需要向树中插入结点。红黑树的插入五种情况进行讨论。

插入操作

 

为什么要分为五种情况讨论呢?我们先来观察一下当一个结点插入到一颗树中有几种情况,首先要明确的一点是插入的结点默认为红色,因为根结点必须为黑色,如果你插入的结点都是黑色,那么红色结点怎么来的?因此先默认插入结点是红色,然后根据树的情况进行变色等相关操作。

1.第一种情况

第一种情况是本来没有树,你这个结点插入了进来也就有了树,即当前插入结点为第一个结点,并且将此结点作为根结点,由第二大性质(根结点必须为黑色),将此结点由红色染成黑色,插入结束。

2.第二种情况

第二种情况是插入结点的父结点是黑色,则直接插入,不需要改变,为什么呢?由于插入的结点是红色的,插入作黑色结点的孩子结点,首先第1,2,3性质没有违反,不需要考虑,只要考虑第4,5性质,第四个性质说的是红色结点的左右孩子必须为黑色,由于父结点是黑色的,所以满足,而插入的结点是红色的,它的左右孩子即叶子结点,默认为黑,所以满足。再看第五个性质,从任意结点开始到其每个叶子节点的所有路径中都包含相同数量的黑色结点。由于插入的是红色结点,所以不影响其他结点到叶子结点的黑色结点数量。因此五大性质都满足。故直接插入即可。即向上面的红黑树中插入一个数值为23的结点:

由于上面一种情况说明只要插入的父结点是黑色就可以直接插入,不需做任何改变,所以后面的都是基于父结点是红色结点进行讨论。

3.第三种情况

第三种情况是新插入的结点的叔叔结点是红色的,则说明其父结点与叔叔结点都是红色的,故将父结点与叔叔结点全都染黑,并将祖父结点染红(根据第4个性质,祖父结点一定是黑色的),再依次向上调整。

分析:新插入的结点是红色的,父结点也是黑色的,那么违反第4条性质(红色结点的孩子一定是黑色的),所以必须要有一个结点染成黑色的,也许会有这样的疑问,将新插入的结点染黑不就成了么,那样做就违反了第5条性质(从任一结点到其每个叶子的所有路径经过的黑色结点数量相同)比如9结点到8结点右孩子的叶子结点经过了一个黑色结点,如果插入了一个7结点作为8的叶子结点,那9结点到7结点的左孩子这一叶子节点就经过了两个黑色结点。所以任何一个插入操作都不能简单的将新插入的结点染黑就获得平衡了。

那么只有将新插入

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值