简介
上一篇Set提到了,底层实现都是靠的Map,学Map前需要有一定的知识储备。
Map维护的其实就是key、value的映射,后面重点讲Map的几个实现:HashMap、TreeMap、LinkedHashMap、ConcurrentHashMap。
其中不得不提到的就是hash。
Hash散列表
之前List中的数组和链表,查找某个元素都存在一个问题,需要遍历所有元素,直到找到元素为止,效率比较低。
而散列表则是不在意存储顺序,只希望快速找到元素。
原理
每个对象计算散列码,根据散列码保存在对应的位置上。出现哈希冲突的时候就往链表上添加,元素冲突的多的话转为红黑树。如果散列表太满,需要再散列,创建一个更大的散列表,把元素放进新的散列表,原散列表丢弃。
装填因子决定何时再散列,默认为0.75。
红黑树
参考自安卓大叔的博客30张图带你彻底理解红黑树在线红黑树
红黑树操作包含两种:旋转,反色
需要遵守的约束:
- 每个节点要不是黑色,要不是红色
- 根节点是黑色的
- 每个叶子节点是黑色的
- 每个红色节点的两个子节点一定都是黑色的
- 任意一节点到每个子节点的路径都包含数量相同的黑节点。
5.1 如果一个节点存在黑子节点,那么该节点肯定有两个子节点。
节点的定义:
红黑树自平衡操作:
- 左旋 以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点,右子结点的左子结点变为旋转结点的右子结点,左子结点保持不变。
- 右旋 以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点,左子结点的右子结点变为旋转结点的左子结点,右子结点保持不变。
- 变色 结点的颜色由红变黑或由黑变红。
左旋只影响旋转结点和其右子树的结构,把右子树的结点往左子树挪了。
右旋只影响旋转结点和其左子树的结构,把左子树的结点往右子树挪了。
所以旋转操作是局部的。另外可以看出旋转能保持红黑树平衡的一些端详了:当一边子树的结点少了,那么向另外一边子树“借”一些结点;当一边子树的结点多了,那么向另外一边子树“租”一些结点。
但要保持红黑树的性质,结点不能乱挪,还得靠变色了。怎么变?具体情景又不同变法,后面会具体讲到,现在只需要记住红黑树总是通过旋转和变色达到自平衡。
红黑树查找
因为红黑树是一颗二叉平衡树,并且查找不会破坏树的平衡,所以查找跟二叉平衡树的查找无异:
- 从根结点开始查找,把根结点设置为当前结点
- 若当前结点为空,返回null
- 若当前结点不为空,用当前结点的key跟查找key作比较
- 若当前结点key等于查找key,那么该key就是查找目标,返回当前结点
- 若当前结点key大于查找key,把当前结点的左子结点设置为当前结点,重复步骤2
- 若当前结点key小于查找key,把当前结点的右子结点设置为当前结点,重复步骤2
红黑树插入
包括两个过程:查找插入的位置,插入之后自平衡。
- 从根节点开始查找
- 如果根节点为空,那么插入节点作为根节点,结束
- 如果根节点不为空,把根节点作为当前节点
- 如果当前节点为null,返回当前节点的父节点,结束
- 如果当前节点key值等于查找key,那么当前节点即插入节点,更新该节点的值,结束
- 如果当前节点key值大于查找key,那么当前节点的左子节点设置为当前节点,重复步骤4
- 如果当前节点key值小于查找key,那么当前节点的右子节点设置为当前节点,重复步骤4
- 插入节点是红色,因为如果父节点不为空切为黑色时,红黑树的黑色平衡没有被破坏,不需要自平衡,如果插入节点为黑色,则平衡破坏,必须自平衡
- 定义名称
- 插入情景
情景一:红黑树为空树
红黑树为空树,直接把插入节点作为根节点就行,因为性质1,根节点是黑色,把插入节点变为黑色。
情景二:插入节点的key已存在
把插入节点设置为当前节点的颜色,并把当前节点的值进行更新。
情景三:插入节点的父节点为黑节点
插入节点为红色,不影响平衡,无需自平衡
情景四:插入节点的父节点为红节点
因为父节点为红色,不可能是根节点,所以必然有祖父节点。情况较多,分类讨论:
情景四-1:叔叔节点存在且为红色节点
祖父节点肯定为黑节点,要是红色,肯定两个子节点都是黑色。所以目前看到的从上往下是黑(祖父)红(父)红(插入节点),因为红色节点的两个子节点必须都是黑色,所以要改为红黑红。
即插入节点为红色,父节点变为黑色,祖父节点变为红色,把祖父节点设置为插入节点。如果祖父节点的父节点为黑色,无需做任何处理,如果是红色,则违背性质,需要把祖父节点当作插入节点,继续做自平衡。
如果祖节点正好是根节点,那么把组节点根据红黑红转为红色节点后,要再重新转为黑色节点,变成黑黑红,黑色层数+1
情景四-2:叔叔节点不存在或为黑色节点,且插入节点的父节点为祖父节点的左子节点
叔叔结点非红即为叶子结点(Nil)
情景四-2-1:插入结点是其父结点的左子结点
父节点设置为黑色,祖父节点设置为红色,祖父节点右旋
情景四-2-2:插入结点是其父结点的右子结点
对父节点进行左旋,父节点设置为插入节点,跟4.2.1一样了
情景四-3:叔叔结点不存在或为黑结点,并且插入结点的父亲结点是祖父结点的右子结点
叔叔结点非红即为叶子结点(Nil)
情景四-3-1:插入结点是其父结点的右子结点
父节点设置为黑色,祖父节点设置为红色,祖父节点左旋
情景四-3-2:插入结点是其父结点的左子结点
对父节点进行右旋,父节点设置为插入节点,跟4.3.1一样了
红黑树删除
有点复杂
过程一样,先找到删除的位置,然后自平衡
删除了结点后我们还需要找结点来替代删除结点的位置,不然子树跟父辈结点断开了,除非删除结点刚好没子结点,那么就不需要替代。
二叉树删除结点找替代结点有3种情情景:
- 情景1:若删除结点无子结点,直接删除
- 情景2:若删除结点只有一个子结点,用子结点替换删除结点
- 情景3:若删除结点有两个子结点,用后继结点(大于删除结点的最小结点)替换删除结点
- 情景3的后继结点是大于删除结点的最小结点,也是删除结点的右子树种最左结点。
把二叉树所有结点投射在X轴上,所有结点都是从左到右排好序的,所有目标结点的前后结点就是对应前继和后继结点。 - 删除操作删除的结点可以看作删除替代结点,而替代结点最后总是在树末。
- 删除情景1:替换结点是红色结点
- 我们把替换结点换到了删除结点的位置时,由于替换结点时红色,删除也了不会影响红黑树的平衡,只要把替换结点的颜色设为删除的结点的颜色即可重新平衡。
- 删除情景2:替换结点是黑结点
- 当替换结点是黑色时,我们就不得不进行自平衡处理了。我们必须还得考虑替换结点是其父结点的左子结点还是右子结点,来做不同的旋转操作,使树重新平衡。
- 删除情景2.1:替换结点是其父结点的左子结点
- 删除情景2.1.1:替换结点的兄弟结点是红结点
- 若兄弟结点是红结点,那么根据性质4,兄弟结点的父结点和子结点肯定为黑色,不会有其他子情景,我们按图21处理,得到删除情景2.1.2.3(后续讲解,这里先记住,此时R仍然是替代结点,它的新的兄弟结点SL和兄弟结点的子结点都是黑色)。
- 处理:
- 将S设为黑色
- 将P设为红色
- 对P进行左旋,得到情景2.1.2.3
- 进行情景2.1.2.3的处理
删除情景2.1.2:替换结点的兄弟结点是黑结点
当兄弟结点为黑时,其父结点和子结点的具体颜色也无法确定(如果也不考虑自底向上的情况,子结点非红即为叶子结点Nil,Nil结点为黑结点),此时又得考虑多种子情景。
删除情景2.1.2.1:替换结点的兄弟结点的右子结点是红结点,左子结点任意颜色
即将删除的左子树的一个黑色结点,显然左子树的黑色结点少1了,然而右子树又又红色结点,那么我们直接向右子树“借”个红结点来补充黑结点就好啦,此时肯定需要用旋转处理了。
处理:
- 将S的颜色设为P的颜色
- 将P设为黑色
- 将SR设为黑色
- 对P进行左旋
删除情景2.1.2.2:替换结点的兄弟结点的右子结点为黑结点,左子结点为红结点
兄弟结点所在的子树有红结点,我们总是可以向兄弟子树借个红结点过来,显然该情景可以转换为情景2.1.2.1
处理:
- 将S设为红色
- 将SL设为黑色
- 对S进行右旋,得到情景2.1.2.1
- 进行情景2.1.2.1的处理
删除情景2.1.2.3:替换结点的兄弟结点的子结点都为黑结点
兄弟子树都没红结点“借”了,兄弟帮忙不了,找父母呗,这种情景我们把兄弟结点设为红色,再把父结点当作替代结点,自底向上处理,去找父结点的兄弟结点去“借”。但为什么需要把兄弟结点设为红色呢?显然是为了在P所在的子树中保证平衡(R即将删除,少了一个黑色结点,子树也需要少一个),后续的平衡工作交给父辈们考虑了,还是那句,当每棵子树都保持平衡时,最终整棵总是平衡的。
处理: - 将S设为红色
- 把P作为新的替换结点
- 重新进行删除结点情景处理
删除情景2.2:替换结点是其父结点的右子结点
右边的操作也是方向相反,不做过多说明了,相信理解了删除情景2.1后,肯定可以理解2.2。
删除情景2.2.1:替换结点的兄弟结点是红结点
处理:
- 将S设为黑色
- 将P设为红色
- 对P进行右旋,得到情景2.2.2.3
- 进行情景2.2.2.3的处理
删除情景2.2.2:替换结点的兄弟结点是黑结点
删除情景2.2.2.1:替换结点的兄弟结点的左子结点是红结点,右子结点任意颜色
处理: - 将S的颜色设为P的颜色
- 将P设为黑色
- 将SL设为黑色
- 对P进行右旋
删除情景2.2.2.2:替换结点的兄弟结点的左子结点为黑结点,右子结点为红结点
处理: - 将S设为红色
- 将SR设为黑色
- 对S进行左旋,得到情景2.2.2.1
- 进行情景2.2.2.1的处理
删除情景2.2.2.3:替换结点的兄弟结点的子结点都为黑结点
处理: - 将S设为红色
- 把P作为新的替换结点
- 重新进行删除结点情景处理
综上,红黑树删除后自平衡的处理可以总结为: - 自己能搞定的自消化(情景1)
- 自己不能搞定的叫兄弟帮忙(除了情景1、情景2.1.2.3和情景2.2.2.3)
- 兄弟都帮忙不了的,通过父母,找远方亲戚(情景2.1.2.3和情景2.2.2.3)
这节主要讲了下hash和红黑树原理,对后面详细讲map有帮助。