简介

上一篇Set提到了,底层实现都是靠的Map,学Map前需要有一定的知识储备。
Map维护的其实就是key、value的映射,后面重点讲Map的几个实现:HashMap、TreeMap、LinkedHashMap、ConcurrentHashMap。
其中不得不提到的就是hash。

Hash散列表

之前List中的数组和链表,查找某个元素都存在一个问题,需要遍历所有元素,直到找到元素为止,效率比较低。
而散列表则是不在意存储顺序,只希望快速找到元素。

原理

每个对象计算散列码,根据散列码保存在对应的位置上。出现哈希冲突的时候就往链表上添加,元素冲突的多的话转为红黑树。如果散列表太满,需要再散列,创建一个更大的散列表,把元素放进新的散列表,原散列表丢弃。
装填因子决定何时再散列,默认为0.75。

红黑树

参考自安卓大叔的博客30张图带你彻底理解红黑树在线红黑树 红黑树操作包含两种:旋转,反色
需要遵守的约束:

  1. 每个节点要不是黑色,要不是红色
  2. 根节点是黑色的
  3. 每个叶子节点是黑色的
  4. 每个红色节点的两个子节点一定都是黑色的
  5. 任意一节点到每个子节点的路径都包含数量相同的黑节点。
    5.1 如果一个节点存在黑子节点,那么该节点肯定有两个子节点。
    节点的定义

    红黑树自平衡操作
  • 左旋 以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点,右子结点的左子结点变为旋转结点的右子结点,左子结点保持不变。
  • 右旋 以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点,左子结点的右子结点变为旋转结点的左子结点,右子结点保持不变。
  • 变色 结点的颜色由红变黑或由黑变红。
    左旋只影响旋转结点和其右子树的结构,把右子树的结点往左子树挪了。
    右旋只影响旋转结点和其左子树的结构,把左子树的结点往右子树挪了。
    所以旋转操作是局部的。另外可以看出旋转能保持红黑树平衡的一些端详了:当一边子树的结点少了,那么向另外一边子树“借”一些结点;当一边子树的结点多了,那么向另外一边子树“租”一些结点。

但要保持红黑树的性质,结点不能乱挪,还得靠变色了。怎么变?具体情景又不同变法,后面会具体讲到,现在只需要记住红黑树总是通过旋转和变色达到自平衡。

红黑树查找

因为红黑树是一颗二叉平衡树,并且查找不会破坏树的平衡,所以查找跟二叉平衡树的查找无异:

  1. 从根结点开始查找,把根结点设置为当前结点
  2. 若当前结点为空,返回null
  3. 若当前结点不为空,用当前结点的key跟查找key作比较
  4. 若当前结点key等于查找key,那么该key就是查找目标,返回当前结点
  5. 若当前结点key大于查找key,把当前结点的左子结点设置为当前结点,重复步骤2
  6. 若当前结点key小于查找key,把当前结点的右子结点设置为当前结点,重复步骤2
  7. sharedpreferences 存取map map存储_子树


红黑树插入

包括两个过程:查找插入的位置,插入之后自平衡。

  1. 从根节点开始查找
  2. 如果根节点为空,那么插入节点作为根节点,结束
  3. 如果根节点不为空,把根节点作为当前节点
  4. 如果当前节点为null,返回当前节点的父节点,结束
  5. 如果当前节点key值等于查找key,那么当前节点即插入节点,更新该节点的值,结束
  6. 如果当前节点key值大于查找key,那么当前节点的左子节点设置为当前节点,重复步骤4
  7. 如果当前节点key值小于查找key,那么当前节点的右子节点设置为当前节点,重复步骤4
  8. sharedpreferences 存取map map存储_结点_02


  9. 插入节点是红色,因为如果父节点不为空切为黑色时,红黑树的黑色平衡没有被破坏,不需要自平衡,如果插入节点为黑色,则平衡破坏,必须自平衡
  10. 定义名称
  11. sharedpreferences 存取map map存储_子树_03


  12. 插入情景
  13. sharedpreferences 存取map map存储_父节点_04


情景一:红黑树为空树

红黑树为空树,直接把插入节点作为根节点就行,因为性质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轴上,所有结点都是从左到右排好序的,所有目标结点的前后结点就是对应前继和后继结点。
  • sharedpreferences 存取map map存储_父节点_05

  • 删除操作删除的结点可以看作删除替代结点,而替代结点最后总是在树末。
  • sharedpreferences 存取map map存储_结点_06


  • sharedpreferences 存取map map存储_父节点_07


  • 删除情景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有帮助。