目录

一、红黑树定义

二、节点新增原理:

三、红黑树的生成

2.1 一个节点

2.2 两个节点

2.3 三个节点

2.3.1 第二个节点作为root右子树情况下

2.3.2 第二个节点作为root左子树情况下

四、左旋和右旋

4.1 左旋

4.2 右旋

五、四种情况分析

5.1 情况一变红黑树

5.2 情况二变红黑树

5.3 情况三变红黑树

5.4 情况四变红黑树

5.5 总结

六、源码分析

6.1 链表转换为半成品树

6.2 半成品树转换为红黑树

6.3 二叉搜索树变成红黑树

6.4 旋转

6.4.1 左旋

6.4.2 右旋

6.5 插入新节点


一、红黑树定义

红黑树是一种结点带有颜色属性的二叉查找树,除此还有以下5大性质:
  1. 节点是红色或黑色。
  2. 根是黑色。
  3. 所有叶子都是黑色(叶子是NIL节点,这类节点不可以忽视,否则代码会看不懂)。
  4. 每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
  5. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点(黑色平衡)。

NIL节点:为了红黑树平衡而添加的空节点

二、节点新增原理:

新插入节点默认是红色,如果是黑色的话那么当前分支上就会多出一个黑色节点出来,从而破坏了黑色平衡。

  1. 如果插入的是第一个节点(根节点),红色变黑色。
  2. 如果父节点为黑色,则直接插入,不需要变色。
  3. 如果父节点是红色,没有叔叔节点或者叔叔节点是黑色,则以爷爷节点为支点旋转,旋转之后原来的爷爷节点变红色,原来的父节点变黑色。
  4. 如果父节点为红色,叔叔节点也是红色(此种情況爷爷节点一定是黑色),则父节点和叔叔节点变黑色,爷爷节点变红色(如果爷爷节点是根节点,则再变成黑色),爷爷节点此时需要递归(把爷爷节点当做新插入的节点再次进行比较)。

三、红黑树的生成

2.1 一个节点

当插入一个元素为5的节点时,由于是新插入的节点,所以应该是红色。但是该树只有一个节点,也就是root根节点,根据红黑树定义2可得,该节点变为黑色。

2.2 两个节点

当已经有一个根节点插入第二个节点元素为x时,分为两种情况。当x>5时,该节点为右节点。当x<5时,该节点为左节点。

2.3 三个节点

在已存在的两个节点产生的这两种情况来看,再添加一个元素,会有以下6种情况

2.3.1 第二个节点作为root右子树情况下

2.3.2 第二个节点作为root左子树情况下

上面存在的六种情况,由于其中两种已经是平衡的红黑树所以不需要旋转。其余的四种情况我们要进一步分析,如何旋转才能让他成为红黑树。

四、左旋和右旋

4.1 左旋

左旋:以某个节点作为旋转点,其右子节点变为旋转节点的父节点,右子节点的左子节点变为旋转节点的右子节点,左子节点保持不变。

4.2 右旋

右旋:以某个节点作为旋转点,其左子节点变为旋转节点的父节点,左子节点的右子节点变为旋转节点的左子节点,右子节点保持不变。

五、四种情况分析

5.1 情况一变红黑树

由图可知,明显该树左边太重了,所有的节点都是左子树,那我们应该向右旋转。以元素为10的节点为旋转点,左子节点5变成他的父节点。左子节点5的右子节点变为旋转节点的左子节点,由于是NIL节点所以在此不再画出。然后进行变色。

5.2 情况二变红黑树

由图可知,情况二的树右边太重了,所有的节点都是右子树,那我们应该向左旋转。以元素为5的节点为旋转点,右子节点10变成他的父节点。右子节点10的左子节点变为旋转节点的右子节点,由于是NIL节点所以在此不再画出。然后进行变色。

5.3 情况三变红黑树

如图所示,情况三刚开始我们无法判定是向左旋还是向右旋。那我们就看他的部分子树,元素10节点和元素x节点如果向右旋转生成的树结构那是不是就和情况二一样了。此时节点为5的右子树为x节点,x节点右子树是元素为10的节点。这就与情况二一样了,再通过左旋并变色处理变成红黑树。

5.4 情况四变红黑树

如图所示,元素5的节点和元素x节点先进行左旋,然后整个树结构与情况一一样,再进行右旋,并进行变色处理,就成为了一个红黑树。

5.5 总结

  • 以上情况都是在节点新增原理的前三条基本原理基础上进行分析的。
  • 无论一个红黑树的节点多少,深度多大,当它新增节点的时候,发生颜色冲突,如果符合节点新增原理的第四条那就无需旋转,只要变色就可以成为新的红黑树。其它需要旋转才能解决的场景都是以上四种情况的变形。
  • 红黑树的形成有两个阶段:成为二叉搜索树和旋转变色。

六、源码分析

6.1 链表转换为半成品树

当满足散列表上的一条链表节点数大于等于8时会进入treeifyBin(tab, hash)方法。将Node节点转换为TreeNode节点,但是TreeNode节点之间通过前后指针相连,并不是左右子树相连。所以我称它为半成品树。

final void treeifyBin(Node<K,V>[] tab, int hash) {int n, index; Node<K,V> e;// 散列表为空或者长度小于64时if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)// 散列表进行扩容操作resize();// 否则将链表转换为半成品树(这些树节点由前指针相连)else if ((e = tab[index = (n - 1) & hash]) != null) {TreeNode<K,V> hd = null, tl = null;do {// 根据Node节点创建新的TreeNode节点TreeNode<K,V> p = replacementTreeNode(e, null);// 尾指针为null时,说明树还未创建if (tl == null)// 头指针赋值给第一个树节点hd = p;else {// 新插入的树节点的前指针指向上一个尾节点p.prev = tl;// 尾节点指向新插入的树节点tl.next = p;}// 尾指针指向最新插入的树节点tl = p;} while ((e = e.next) != null); // 遍历下一个节点// 半成品树的头指针赋值给散列表对应位置if ((tab[index] = hd) != null)// 转换为红黑树hd.treeify(tab);}}

由上可知,节点转换为红黑树的两个条件:

  1. 链表节点数大于等于8
  2. 散列表长度大于等于64

6.2 半成品树转换为红黑树

treeify(Node<K,V>[] tab)方法就可以分为先成为一个二叉搜索树,再调用balanceInsertion(root, x)方法通过旋转变色成为红黑树。

    final void treeify(Node<K,V>[] tab) {TreeNode<K,V> root = null;// 遍历循环半成品树节点for (TreeNode<K,V> x = this, next; x != null; x = next) {// 头节点指针的下一个节点是第一个树节点next = (TreeNode<K,V>)x.next;x.left = x.right = null;// 当没有根节点的时候,创建根节点,并成黑色if (root == null) {x.parent = null;x.red = false;root = x;}// 否则不是根节点的时候else {K k = x.key;int h = x.hash;Class<?> kc = null;// 遍历已经存在的树节点for (TreeNode<K,V> p = root;;) {int dir, ph;K pk = p.key;// 所遍历的树节点hash值大于要插入的节点hash值,向左子树继续遍历if ((ph = p.hash) > h)dir = -1;// 所遍历的树节点hash值小于要插入的节点hash值,向右子树继续遍历else if (ph < h)dir = 1;// 如果要插入的节点hash值等于遍历所在节点hash,hash相等时,通过内存地址进行比较else if ((kc == null &&(kc = comparableClassFor(k)) == null) ||(dir = compareComparables(kc, k, pk)) == 0)//说明红黑树中没有与之相等的  那就必须进行插入操作。// 分出插入节点是左节点还是右节点dir = tieBreakOrder(k, pk);TreeNode<K,V> xp = p;// 根据dir区分要继续遍历左节点还是右节点// 当下一个节点为null的时候说明已经找到要插入的树节点所在的位置if ((p = (dir <= 0) ? p.left : p.right) == null) {// 要插入的树节点父指针 指向 调整成树后遍历所得树节点x.parent = xp;// 根据dir区分出插入节点放入左节点还是右节点if (dir <= 0)xp.left = x;elsexp.right = x;// 插入完成后是一个二叉搜索树,需要变色或旋转成为红黑树root = balanceInsertion(root, x);break;}}}}// 检验root节点是不是第一个节点moveRootToFront(tab, root);}

6.3 二叉搜索树变成红黑树

这里是从叶节点遍历到root根节点,从部分到整体一步步满足红黑树的条件。新插入的节点根据是父节点的左子树还是右子树,以及父节点、爷爷节点和叔叔节点的颜色可以分为不同的情况,根据不同的情况分别进行左旋和右旋。

rotateLeft(TreeNode<K,V> root,TreeNode<K,V> p)是进行左旋转。

rotateRight(TreeNode<K,V> root,TreeNode<K,V> p)是进行右旋转。

static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,TreeNode<K,V> x) {// 此处就是节点新增原理提到的新插入节点默认为红色x.red = true;// 遍历树x节点一直到root节点for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {// 如果是根节点if ((xp = x.parent) == null) {// 变为黑色x.red = false;return x;}//如果该节点父节点是黑色,或者父节点为根节点else if (!xp.red || (xpp = xp.parent) == null)return root;// 如果父节点是爷爷节点的左子树if (xp == (xppl = xpp.left)) {// 如果叔叔节点不为空并且是红色//      xpp//     /   \//   xp(R) Redif ((xppr = xpp.right) != null && xppr.red) {xppr.red = false;xp.red = false;xpp.red = true;x = xpp;}// 如果叔叔节点为空或者不为空是黑色else {// 如果该节点是右节点//      xpp             xpp//     /   \           ///   xp(R) black     xp(R)//     \              \//      x(R)           x(R)if (x == xp.right) {// 左旋root = rotateLeft(root, x = xp);xpp = (xp = x.parent) == null ? null : xp.parent;}// 如果该节点是左节点//      xpp             xpp//     /   \            ///   xp(R) black     xp(R)//   /              ///  x(R)           x(R)if (xp != null) {xp.red = false;if (xpp != null) {xpp.red = true;//      xpp(R)          xpp(R)//     /   \            ///    xp(B) black   xp(B)//   /              ///  x(R)           x(R)// 右旋将的到的新树赋给root,再次遍历root = rotateRight(root, xpp);}}}}// 如果父节点是爷爷节点的右子树else {// 如果叔叔节点不为空并且是红色//             xpp//           /   \//         Red  xp(R)if (xppl != null && xppl.red) {xppl.red = false;xp.red = false;xpp.red = true;x = xpp;}// 如果叔叔节点为空或者不为空是黑色else {// 如果该节点是左节点//      xpp             xpp//         \           /   \//        xp(R)     black xp(R)//          /              ///        x(R)           x(R)if (x == xp.left) {// 右旋root = rotateRight(root, x = xp);xpp = (xp = x.parent) == null ? null : xp.parent;}// 如果该节点是右节点//      xpp             xpp//         \           /   \//        xp(R)     black xp(R)//          \               \//          x(R)            x(R)if (xp != null) {xp.red = false;if (xpp != null) {xpp.red = true;//      xpp(R)          xpp(R)//         \           /   \//        xp(B)     black xp(B)//          \               \//          x(R)            x(R)// 左旋root = rotateLeft(root, xpp);}}}}}}

6.4 旋转

我根据源码将不同的情况下的左旋或右旋结果,用注释表示了出来。大家可以与第五节那四种情况结合分析。

6.4.1 左旋

static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,TreeNode<K,V> p) {TreeNode<K,V> r, pp, rl;if (p != null && (r = p.right) != null) {// p的右节点指向r的左孩子(即rl),如果rl不为空,其父节点指向p;//            p//             \//              r//             ///            rlif ((rl = p.right = r.left) != null)rl.parent = p;//         r//       ///      p//------------------------------------// p节点为根节点,直接root指向r,同时颜色置为黑色(根节点颜色都为黑色)if ((pp = r.parent = p.parent) == null)(root = r).red = false;// 如果该节点是右节点//       pp             pp//     /   \           ///    p(R) black     p(R)//     \              \//      r(R)           r(R)else if (pp.left == p)pp.left = r;// 走完该方法图形后的//        pp             pp//      /   \           ///    r(R) black     r(R)//    /              ///  p(R)            p(R)//---------------------------------//      pp              pp//       \               \//      p(R)            p(R)//         \           /   \//         r(B)     black  r(B)//          \               \//          x(R) t           x(R)elsepp.right = r;// 走完该方法后的图形//      pp              pp//       \               \//       r(B)           r(B)//      /  \           /   \//   p(R)  x(R)     p(R)  x(R)//                   ///                blackr.left = p;p.parent = r;}return root;}

6.4.2 右旋

static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,TreeNode<K,V> p) {TreeNode<K,V> l, pp, lr;if (p != null && (l = p.left) != null) {// p的左节点指向l的右孩子(即lr),如果lr不为空,其父节点指向p;//            p//          ///         l//          \//          lrif ((lr = p.left = l.right) != null)lr.parent = p;//         l//          \//           p//------------------------------------// 如果pp为null,说明p节点为根节点,直接root指向l,同时颜色置为黑色(根节点颜色都为黑色)if ((pp = l.parent = p.parent) == null)(root = l).red = false;//        pp             pp//         \           /   \//        p(R)     black  p(R)//          /              ///        l(R)           l(R)else if (pp.right == p)pp.right = l;// 走完该方法后的图形//        pp             pp//         \           /   \//        l(R)     black  l(R)//          \               \//          p(R)            p(R)// --------------------------------------//        pp(B)            pp(B)//        /               ///      p(R)            p(R)//     /   \            ///    l(B) black     l(B)//   /              ///  x(R)           x(R)elsepp.left = l;// 走完该方法后的图形//        pp(B)           pp(B)//        /               ///      l(B)            l(B)//     /   \            /  \//    x(R) p(R)      x(R) p(R)//           \//          blackl.right = p;p.parent = l;}return root;}

6.5 插入新节点

插入新节点从root节点往下遍历分为4种情况:

  1. 要插入的节点hash值小于遍历所在节点hash,遍历左子树
  2. 要插入的节点hash值大于遍历所在节点hash,遍历右子树
  3. 要插入的节点hash值等于遍历所在节点hash,并且key值相等返回该节点
  4. 要插入的节点hash值等于遍历所在节点hash,但是key不等时,发生hash冲突。此时又分为两种情况。
    1. 遍历该节点的左右子节点是否存在hash相等,并且key也相等的节点,有则返回该节点
    2. 如果没有则调用tieBreakOrder(k, pk)方法,比较key值,确定是遍历左子树还是右子树
final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,int h, K k, V v) {Class<?> kc = null;boolean searched = false;// 获取根节点TreeNode<K,V> root = (parent != null) ? root() : this;for (TreeNode<K,V> p = root;;) {int dir, ph; K pk;// 如果要插入的节点hash值小于遍历所在节点hash,遍历左子树if ((ph = p.hash) > h)dir = -1;// 如果要插入的节点hash值大于遍历所在节点hash,遍历右子树else if (ph < h)dir = 1;// 如果要插入的节点hash值等于遍历所在节点hash,并且// key值相等返回该节点else if ((pk = p.key) == k || (k != null && k.equals(pk)))return p;// 如果要插入的节点hash值等于遍历所在节点hash,但是key不等时,此时发生hash冲突else if ((kc == null &&(kc = comparableClassFor(k)) == null) ||(dir = compareComparables(kc, k, pk)) == 0) {// 在左右子树递归的寻找 是否有key的hash相同  并且equals相同的节点if (!searched) {TreeNode<K,V> q, ch;searched = true;// (ch = p.left) != null 左子树不为空// (ch = p.right) != null 右子树不为空// (q = ch.find(h, k, kc)) != null) 递归查找hash值相等的并且key也相等// 如果找到hash值相等的则返回该节点if (((ch = p.left) != null &&(q = ch.find(h, k, kc)) != null) ||((ch = p.right) != null &&(q = ch.find(h, k, kc)) != null))return q;}//说明红黑树中没有与之相等的  那就必须进行插入操作。// 分出插入节点是左节点还是右节点dir = tieBreakOrder(k, pk);}TreeNode<K,V> xp = p;// 如果dir小于0,那p等于p的左子树节点,不为null则继续遍历// 如果dir大于0,那p等于p的右子树节点,不为null则继续遍历// 当为null时说明是叶子节点则执行下面方法if ((p = (dir <= 0) ? p.left : p.right) == null) {Node<K,V> xpn = xp.next;// 由于TreeNode继承了Node,创建一个新的TreeNode节点将要插入的// hash、key、value存入TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);// dir小于0,新节点为左节点if (dir <= 0)xp.left = x;// dir大于0,新节点为右节点elsexp.right = x;xp.next = x;x.parent = x.prev = xp;if (xpn != null)((TreeNode<K,V>)xpn).prev = x;// balanceInsertion(root, x)方法让一个树成为红黑树,并返回根节点// moveRootToFront,检验root节点是不是第一个节点moveRootToFront(tab, balanceInsertion(root, x));return null;}}}

HashMap红黑树原理及源码分析---图形、注释一应俱全相关推荐

  1. Android技术栈(五)HashMap(包括红黑树)与ArrayMap源码解析

    1 总览 本文会对 Android 中常用HashMap(有红黑树)和ArrayMap进行源码解析,其中 HashMap 源码来自 Android Framework API 28 (JDK=1.8) ...

  2. HashMap红黑树原理解析

    HashMap红黑树原理解析 定义: 简单来说红黑树是一种近视平衡二叉查找树,主要优点是"平衡",即左右子树高度几乎一致,以此来防止树退化为链表,通过这种方式来保障查找的时间复杂度 ...

  3. ConcurrentHashMap实现原理及源码分析

    ConcurrentHashMap是Java并发包中提供的一个线程安全且高效的HashMap实现(若对HashMap的实现原理还不甚了解,可参考我的另一篇文章HashMap实现原理及源码分析),Con ...

  4. concurrenthashmap_ConcurrentHashMap实现原理及源码分析

    ConcurrentHashMap是Java并发包中提供的一个线程安全且高效的HashMap实现(若对HashMap的实现原理还不甚了解,可参考我的另一篇文章HashMap实现原理及源码分析),Con ...

  5. 深入理解Spark 2.1 Core (十一):Shuffle Reduce 端的原理与源码分析

    我们曾经在<深入理解Spark 2.1 Core (一):RDD的原理与源码分析 >讲解过: 为了有效地实现容错,RDD提供了一种高度受限的共享内存,即RDD是只读的,并且只能通过其他RD ...

  6. 深入理解Spark 2.1 Core (七):Standalone模式任务执行的原理与源码分析

    这篇博文,我们就来讲讲Executor启动后,是如何在Executor上执行Task的,以及其后续处理. 执行Task 我们在<深入理解Spark 2.1 Core (三):任务调度器的原理与源 ...

  7. 深入理解Spark 2.1 Core (六):Standalone模式运行的原理与源码分析

    我们讲到了如何启动Master和Worker,还讲到了如何回收资源.但是,我们没有将AppClient是如何启动的,其实它们的启动也涉及到了资源是如何调度的.这篇博文,我们就来讲一下AppClient ...

  8. 【转】HashMap,ArrayMap,SparseArray源码分析及性能对比

    HashMap,ArrayMap,SparseArray源码分析及性能对比 jjlanbupt 关注 2016.06.03 20:19* 字数 2165 阅读 7967评论 13喜欢 43 Array ...

  9. SIFT原理与源码分析:DoG尺度空间构造

    <SIFT原理与源码分析>系列文章索引:http://blog.csdn.net/xiaowei_cqu/article/details/8069548 尺度空间理论 自然界中的物体随着观 ...

最新文章

  1. Info:Memory module [DIMM] needs attention: Single-bit warning error rate exceeded, Single-bit fai...
  2. 小孩学python有意义吗-世界冠军教练告诉你:少儿编程这些坑,能不踩就别踩!...
  3. 【活动推荐】北京泛娱乐行业技术沙龙——新技术助力内容产业破局之道
  4. JS排序算法之插入排序
  5. 关于memcached的收集
  6. 在word中怎么把文字往下挪挪_ps怎么挪动文字位置
  7. adfs服务器获取信息失败,在ADFS服务器上SAML LogOutRequest处理失败
  8. (转)MyBatis框架的学习(二)——MyBatis架构与入门
  9. 计算机组成原理实验存储器部件实验,计算机组成原理实验_存储器部件教学实验...
  10. linux下apache+php配置
  11. Andromeda OS 来了,Android 再见?
  12. Linux下informix的安装(最简单超详细)
  13. 如何下载小程序图片?
  14. 【制作脑图】万彩脑图大师教程 | 概括主题
  15. IOS之UIImageView--小实例项目--带音效的拳皇动画
  16. 电子海图浮标信息的计算和输入
  17. edge浏览器安装chrome插件
  18. 一路狂奔的硅谷钢铁侠,终于露出脆弱与疲倦,压力焦虑直播吸大麻
  19. 充电器充满变灯电路图(五款充电器充满变灯指示电路详细)一般充电器的工作原理是稳压限流。根据检测到的充电电流变化来实现红转绿的,不同容量的蓄电池所要设定转灯的电流值不同
  20. 树上摩托【NOIP2016提高A组模拟9.4】

热门文章

  1. 我骑方轮车笑对坑洼人生
  2. 如何改变对话框的背景颜色
  3. CentOS 7修改SSH端口
  4. 股票顺口溜(幽默版)
  5. sql 连表修改数据
  6. Unity换装系统(SkinnedMeshRenderer)
  7. 毕业设计 基于单片机的智能窗户系统(源码+硬件+论文)
  8. ipad和iphone适配_适用于iPhone,iPad和Android的最佳“类似控制台”游戏
  9. IIS 7的配置问题
  10. 微信或系统字体放大缩小,html页面布局错乱问题