• 红黑树原理和算法介绍


    转载 红黑树(一)之 原理和算法详细介绍  30 张图带你彻底理解红黑树

    一、红黑树介绍

    什么是红黑树?

      红黑树是一种自平衡二叉查找树,是计算机科学领域中的一种数据结构,典型的用途是实现关联数组,存储有序的数据。它是在1972年由Rudolf Bayer发明的,别称"对称二叉B树",它现代的名字由 Leo J. Guibas 和 Robert Sedgewick 于1978年写的一篇论文中获得的。它是复杂的,但它的操作有着良好的最坏情况运行时间,并且在实践中是高效的。它可以在O(logn)时间内做查找,插入和删除,这里的n是树的结点个数。

      红黑树和平衡二叉树(AVL树)都是二叉查找树的变体,但红黑树的统计性能要好于AVL树。因为,AVL树是严格维持平衡的,红黑树是黑平衡的。维持平衡需要额外的操作,这就加大了数据结构的时间复杂度,所以红黑树可以看作是二叉搜索树和AVL树的一个折中,维持平衡的同时也不需要花太多时间维护数据结构的性质。红黑树在很多地方都有应用,例如:

    • C++的STL,map和set都是用红黑树实现的。
    • 著名的linux进程调度Completely Fair Scheduler,用红黑树管理进程控制块。
    • epoll在内核中的实现,用红黑树管理事件块。
    • nginx用红黑树管理timer等。
    • Java的TreeMap实现。

    红黑树简介:

      R-B Tree,全称是Red-Black Tree,又称为“红黑树”,是一种特殊的二叉查找树。红黑树的每个结点上都有存储位表示结点的颜色,可以是红(Red)或黑(Black)。

    红黑树的特性:

    1. 每个结点是黑色或者红色。
    2. 根结点是黑色。
    3. 每个叶子结点(NIL)是黑色。 [注意:这里叶子结点,是指为空(NIL或NULL)的叶子结点!]
    4. 如果一个结点是红色的,则它的子结点必须是黑色的。
    5. 每个结点到叶子结点NIL所经过的黑色结点的个数一样的。[确保没有一条路径会比其他路径长出俩倍,所以红黑树是相对接近平衡的二叉树的!]

     图1(红黑树)

    二、红黑树基本操作  

      红黑树的基本操作是添加、删除。在对红黑树进行添加或删除之后,都会用到旋转方法。为什么呢?道理很简单,添加或删除红黑树中的结点之后,红黑树的结构就发生了变化,可能不满足红黑树的5条性质,也就不再是一颗红黑树了,而是一颗普通的树。而通过旋转和变色,可以使这颗树重新成为红黑树。简单点说,旋转和变色的目的是让树保持红黑树的特性:自平衡二叉树
    旋转包括两种:左旋 和 右旋。下面分别对它们进行介绍:

    左旋:以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点,右子结点的子结点变为旋转结点的右子结点,其左子结点保持不变。如图2。

    右旋:以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点,左子结点的子结点变为旋转结点的左子结点,其右子结点保持不变。如图3。

    变色:结点的颜色由红变黑或由黑变红。

    1.左旋

     图2(左旋图)

    左旋算法:

    LEFT-ROTATE(T, x)  
     y ← right[x]            // 前提:这里假设x的右孩子为y。下面开始正式操作
     right[x] ← left[y]      // 将 “y的左孩子” 设为 “x的右孩子”
     p[left[y]] ← x          // 将 “x” 设为 “y的左孩子的父亲”
     p[y] ← p[x]             // 将 “x的父亲” 设为 “y的父亲”
     if p[x] = nil[T]       
     then root[T] ← y                 // 情况1:如果 “x的父亲” 是空节点,则将y设为根节点
     else if x = left[p[x]]  
               then left[p[x]] ← y    // 情况2:如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
               else right[p[x]] ← y   // 情况3:(x是它父节点的右孩子) 将y设为“x的父节点的右孩子”
     left[y] ← x             // 将 “x” 设为 “y的左孩子”
     p[x] ← y                // 将 “x的父节点” 设为 “y”

    2.右旋

     图3(右旋图)

    右旋算法:

    RIGHT-ROTATE(T, y)  
     x ← left[y]             // 前提:这里假设y的左孩子为x。下面开始正式操作
     left[y] ← right[x]      // 将 “x的右孩子” 设为 “y的左孩子”
     p[right[x]] ← y         // 将 “y” 设为 “x的右孩子的父亲”
     p[x] ← p[y]             // 将 “y的父亲” 设为 “x的父亲”
     if p[y] = nil[T]       
     then root[T] ← x                 // 情况1:如果 “y的父亲” 是空结点,则将x设为根结点
     else if y = right[p[y]]  
               then right[p[y]] ← x   // 情况2:如果 y是它父结点的右孩子,则将x设为“y的父结点的左孩子”
               else left[p[y]] ← x    // 情况3:(y是它父结点的左孩子) 将x设为“y的父结点的左孩子”
     right[x] ← y            // 将 “y” 设为 “x的右孩子”
     p[y] ← x                // 将 “y的父结点” 设为 “x”

    我们先忽略颜色,可以看到旋转操作不会影响旋转结点的父结点,父结点以上的结构还是保持不变的。

      左旋只影响旋转结点和其右子树的结构,把右子树的结点往左子树挪了。

      右旋只影响旋转结点和其左子树的结构,把左子树的结点往右子树挪了。

      所以旋转操作是局部的。另外可以看出旋转能保持红黑树平衡的一些端详了:当一边子树的结点少了,那么向另外一边子树“借”一些结点;当一边子树的结点多了,那么向另外一边子树“租”一些结点。

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

    3.添加

    图4(插入结点流程图)

    插入算法:

    RB-INSERT(T, z)  
     y ← nil[T]                        // 新建结点“y”,将y设为空结点。
     x ← root[T]                       // 设“红黑树T”的根结点为“x”
     while x ≠ nil[T]                  // 找出要插入的结点“z”在二叉树T中的位置(父结点),即“y”结点要存放的位置
         do y ← x                      
            if key[z] < key[x]  
               then x ← left[x]  
               else x ← right[x]  
     p[z] ← y                          // 设置 “z的父亲” 为 “y”
     if y = nil[T]                     
        then root[T] ← z               // 情景1:若y是空结点,则将z设为根结点
        else if key[z] < key[y]        
                then left[y] ← z       // 情景2:若“z的key值” < “y的key值”,则将z设为“y的左孩子”
                else right[y] ← z      // 情景2:若“z的key值” >= “y的key值”,则将z设为“y的右孩子” 
     left[z] ← nil[T]                  // z的左孩子设为空
     right[z] ← nil[T]                 // z的右孩子设为空。至此,已经完成将“结点z插入到二叉树”中了。
     color[z] ← RED                    // 将z着色为“红色”
     RB-INSERT-FIXUP(T, z)             // 通过RB-INSERT-FIXUP对红黑树的结点进行颜色修改以及旋转,让树T仍然是一颗红黑树

    插入修正算法:

    RB-INSERT-FIXUP(T, z)
     while color[p[z]] = RED                                            // 若“当前结点(z)的父结点是红色”,则进行以下处理。
        do if p[z] = left[p[p[z]]]                                      // 若“z的父结点”是“z的祖父结点的左孩子”,则进行以下处理。
              then y ← right[p[p[z]]]                                   // 将y设置为“z的叔叔结点(z的祖父结点的右孩子)”
                if color[y] = RED                                       // 4.1情景:叔叔是红色
                  then color[p[z]] ← BLACK                              //  (01) 将“父结点”设为黑色。
                       color[y] ← BLACK                                 //  (02) 将“叔叔结点”设为黑色。
                       color[p[p[z]]] ← RED                             //  (03) 将“祖父结点”设为“红色”。
                       z ← p[p[z]]                                      //  (04) 将“祖父结点”设为“当前结点”(红色结点)
                  else if z = right[p[z]]                               // 4.3.1情景:叔叔是黑色,且当前结点是右孩子
                        then z ← p[z]                                   //  (01) 将“父结点”作为“新的当前结点”。
                            LEFT-ROTATE(T, z)                           //  (02) 以“新的当前结点”为支点进行左旋。
                        color[p[z]] ← BLACK                             // 4.2.1情景:叔叔是黑色,且当前结点是左孩子。(01) 将“父结点”设为“黑色”。
                        color[p[p[z]]] ← RED                            //  (02) 将“祖父结点”设为“红色”。
                        RIGHT-ROTATE(T, p[p[z]])                        //  (03) 以“祖父结点”为支点进行右旋。
           else (same as then clause with "right" and "left" exchanged) // 若“z的父结点”是“z的祖父结点的右孩子”,将上面的操作中“right”和“left”交换位置,然后依次执行。
     color[root[T]] ← BLACK                                             // 情景1:若y是空结点,则将z设为根结点

      但插入结点是为什么是红色呢?理由很简单,红色在父结点(如果存在)为黑色结点时,红黑树的黑色平衡没被破坏,不需要做自平衡操作。但如果插入结点是黑色,那么插入位置所在的子树黑色结点总是多1,必须做自平衡。

    插入情况可以总结为下面这些:

    情景1:
    红黑树为空树
    最简单的一种情景,直接把插入结点作为根结点就行,但注意,根据红黑树性质2:根结点是黑色。所以还需要把插入结点设为黑色。
    处理:

    • 把插入结点作为根结点,并把结点设置为黑色。

    情景2:
    插入结点的Key已存在
    插入结点的Key已存在,因为红黑树总保持平衡,在插入前红黑树已经是平衡的,那么把插入结点设置为将要替代的结点颜色,再把结点的值更新就完成插入了。
    处理:

    • 把z设为当前结点的颜色。
    • 更新当前结点的值为插入结点的值。

    情景3:
    插入结点的父结点为黑结点
    由于插入的结点是红色的,并不会影响红黑树的平衡,直接插入即可,无需做自平衡。
    处理:

    • 直接插入。

    情景4:
    插入结点的父结点为红结点
    再次回想下红黑树的性质2:根结点是黑色。如果插入的父结点为红结点,那么该父结点不可能为根结点,所以插入结点总是存在祖父结点。这点很重要,因为后续的旋转操作需要祖父结点的参与。

    情景4.1:
    叔叔结点存在并且为红结点
    从红黑树性质4可以确定,祖父结点为黑结点,因为不可以同时存在两个相连的红结点。那么此时该插入子树的红黑层数的情况是:黑红红。显然最简单的处理方式是把其改为:红黑红。如图5和图6所示。
    处理:

    • 将P和S设置为黑色(当前插入结点I)
    • 将PP设置为红色
    • 把PP设置为当前插入结点

     

    图5

     

    图6

    红黑树的生长是自底向上。这点不同于普通的二叉查找树,普通的二叉查找树的生长是自顶向下的。

    情景4.2:
    叔叔结点不存在或为黑结点,并且插入结点的父亲结点是祖父结点的左子结点

    单纯从插入前来看,也即不算情景4.1自底向上处理时的情况,叔叔结点非红即为叶子结点(Nil)。因为如果叔叔结点为黑结点,而父结点为红结点,那么叔叔结点所在的子树的黑色结点就比父结点所在子树的多了,这不满足红黑树的性质5。后续情景同样如此,不再多做说明了。

    情景4.2.1:
    插入结点是其父结点的左子结点
    处理:

    • 将P设为黑色 
    • 将PP设为红色
    • 对PP进行右旋

    图7

    咦,可以把PP设为红色,I和P设为黑色吗?答案是可以!看过《算法:第4版》的同学可能知道,书中讲解的就是把PP设为红色,I和P设为黑色。但把PP设为红色,显然又会出现情景4.1的情况,需要自底向上处理,做多了无谓的操作,既然能自己消化就不要麻烦祖辈们啦~

    情景4.2.2:
    插入结点是其父结点的右子结点
    这种情景显然可以转换为情景4.2.1,如图12所示,不做过多说明了。
    处理:

    • 对P进行左旋
    • 把P设置为插入结点,得到情景4.2.1
    • 进行情景4.2.1的处理

    图8

    情景4.3:
    叔叔结点不存在或为黑结点,并且插入结点的父亲结点是祖父结点的右子结点
    该情景对应情景4.2,只是方向反转,不做过多说明了,直接看图。

    情景4.3.1:
    插入结点是其父结点的右子结点
    处理:

    • 将P设为黑色
    • 将PP设为红色
    • 对PP进行左旋

    图9

    情景4.3.2:
    插入结点是其父结点的左子结点
    处理:

    • 对P进行右旋
    • 把P设置为插入结点,得到情景4.3.1
    • 进行情景4.3.1的处理

    图10

    4.删除

    红黑树的删除操作包括两部分工作:

    • 查找目标结点。
    • 删除结点后自平衡。

    查找目标结点显然可以复用查找操作,当不存在目标结点时,忽略本次操作;当存在目标结点时,删除后需要做自平衡处理。删除结点后我们需要找结点来替代删除结点的位置,不然子树跟父辈结点断开了,除非删除结点刚好没子结点,那么就不需要替代。

    二叉树删除结点找替代结点有3种情情景:

    情景1:若删除结点无子结点,直接删除。

    情景2:若删除结点只有一个子结点,用子结点替换删除结点。

    情景3:若删除结点有两个子结点,用后继结点(大于删除结点的最小结点)替换删除结点。

      情景3中可以用前继结点(小于删除结点的最大结点)替代删除结点吗?可以的。但习惯上大多都是拿后继结点来替代,后文的讲解也是用后继结点来替代。另外告诉大家一种找前继和后继结点的直观的方法(不知为何没人提过,大家都知道?):把二叉树所有结点投射在X轴上,所有结点都是从左到右排好序的,所有目标结点的前后结点就是对应的前继和后继结点。如图11所示。

    图11

    接下来,讲一个重要的思路:删除结点被替代后,在不考虑结点的键值的情况下,对于树来说,可以认为删除的是替代结点!话很苍白,我们看图12。在不看键值对的情况下,图12的红黑树最终结果是删除了Q所在位置的结点!这种思路非常重要,大大简化了后文讲解红黑树删除的情景!

    图12

    基于此,上面所说的3种二叉树的删除情景可以相互转换并且最终都是转换为情景1!!
    情景2:删除结点用其唯一的子结点替换,子结点替换为删除结点后,可以认为删除的是子结点,若子结点又有两个子结点,那么相当于转换为情景3,一直自顶向下转换,总是能转换为情景1。(根据红黑树的性质来说,只存在一个子结点的结点肯定在树末了)
    情景3:删除结点用后继结点(后继结点肯定不存在左结点),如果后继结点有右子结点,那么相当于转换为情景2,否则转为为情景1。

    图13

    删除算法:

    RB-DELETE(T, z)
    if left[z] = nil[T] or right[z] = nil[T]         
       then y ← z                                  // 若“z的左孩子” 或 “z的右孩子”为空,则将“z”赋值给 “y”;
       else y ← TREE-SUCCESSOR(z)                  // 否则,将“z的后继节点”赋值给 “y”。
    if left[y] ≠ nil[T]
       then x ← left[y]                            // 若“y的左孩子” 不为空,则将“y的左孩子” 赋值给 “x”;
       else x ← right[y]                           // 否则,“y的右孩子” 赋值给 “x”。
    p[x] ← p[y]                                    // 将“y的父节点” 设置为 “x的父节点”
    if p[y] = nil[T]                               
       then root[T] ← x                            // 若“y的父节点” 为空,则设置“x” 为 “根节点”。
       else if y = left[p[y]]                    
               then left[p[y]] ← x                 // 若“y是它父节点的左孩子”,则设置“x” 为 “y的父节点的左孩子”
               else right[p[y]] ← x                // 若“y是它父节点的右孩子”,则设置“x” 为 “y的父节点的右孩子”
    if y ≠ z                                    
       then key[z] ← key[y]                        // 若“y的值” 赋值给 “z”。注意:这里只拷贝z的值给y,而没有拷贝z的颜色!!!
            copy y's satellite data into z         
    if color[y] = BLACK                            
       then RB-DELETE-FIXUP(T, x)                  // 若“y为黑节点”,即替换结点是黑色则调用删除修正算法
    return y

    删除修正算法:

    删除修正算法:
    RB-DELETE-FIXUP(T, x)
    while x ≠ root[T] and color[x] = BLACK  
        do if x = left[p[x]]      
              then w ← right[p[x]]                                             // 若 “x”是“它父节点的左孩子”,则设置 “w”为“x的兄弟”(即x为它父节点的右孩子)                                          
                   if color[w] = RED                                           // 情景2.1.1: x是“黑+黑”节点,x的兄弟节点是红色。(此时x的父节点和x的兄弟节点的子节点都是黑节点)。
                      then color[w] ← BLACK                                    //   (01) 将x的兄弟节点设为“黑色”。
                           color[p[x]] ← RED                                   //   (02) 将x的父节点设为“红色”。
                           LEFT-ROTATE(T, p[x])                                //   (03) 对x的父节点进行左旋。
                           w ← right[p[x]]                                     //   (04) 左旋后,重新设置x的兄弟节点。
                   if color[left[w]] = BLACK and color[right[w]] = BLACK       // 情景2.1.2.3: x是“黑+黑”节点,x的兄弟节点是黑色,x的兄弟节点的两个孩子都是黑色。
                      then color[w] ← RED                                      //   (01) 将x的兄弟节点设为“红色”。
                           x ←  p[x]                                           //   (02) 设置“x的父节点”为“新的x节点”。
                      else if color[right[w]] = BLACK                          // 情景2.1.2.2: x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的左孩子是红色,右孩子是黑色的。
                              then color[left[w]] ← BLACK                      //   (01) 将x兄弟节点的左孩子设为“黑色”。
                                   color[w] ← RED                              //   (02) 将x兄弟节点设为“红色”。
                                   RIGHT-ROTATE(T, w)                          //   (03) 对x的兄弟节点进行右旋。
                                   w ← right[p[x]]                             //   (04) 右旋后,重新设置x的兄弟节点。
                            color[w] ← color[p[x]]                             // 情景2.1.2.1: x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的右孩子是红色的。(01) 将x父节点颜色 赋值给 x的兄弟节点。
                            color[p[x]] ← BLACK                                //   (02) 将x父节点设为“黑色”。
                            color[right[w]] ← BLACK                            //   (03) 将x兄弟节点的右子节设为“黑色”。
                            LEFT-ROTATE(T, p[x])                               //   (04) 对x的父节点进行左旋。
                            x ← root[T]                                        //   (05) 设置“x”为“根节点”。
           else (same as then clause with "right" and "left" exchanged)        // 若 “x”是“它父节点的右孩子”,将上面的操作中“right”和“left”交换位置,然后依次执行。
    color[x] ← BLACK

    图14

    图中字母并不代表结点Key的大小。R表示替代结点,P表示替代结点的父结点,S表示替代结点的兄弟结点,SL表示兄弟结点的左子结点,SR表示兄弟结点的右子结点。灰色结点表示它可以是红色也可以是黑色。
    值得特别提醒的是,R是即将被替换到删除结点的位置的替代结点,在删除前,它还在原来所在位置参与树的子平衡,平衡后再替换到删除结点的位置,才算删除完成。

    删除情况可以总结为下面这些:

    情景1:
    替换结点是红色结点
    我们把替换结点换到了删除结点的位置时,由于替换结点是红色,删除也了不会影响红黑树的平衡,只要把替换结点的颜色设为删除的结点的颜色即可重新平衡。如图12。
    处理:

    • 颜色变为删除结点的颜色

    情景2:
    替换结点是黑结点
    当替换结点是黑色时,我们就不得不进行自平衡处理了。我们必须还得考虑替换结点是其父结点的左子结点还是右子结点,来做不同的旋转操作,使树重新平衡。

    情景2.1:
    替换结点是其父结点的左子结点

    情景2.1.1:
    替换结点的兄弟结点是红结点
    若兄弟结点是红结点,那么根据性质4,兄弟结点的父结点和子结点肯定为黑色,不会有其他子情景,我们按图15处理,得到情景2.1.2.3(后续讲解,这里先记住,此时R仍然是替代结点,它的新的兄弟结点SL和兄弟结点的子结点都是黑色)。
    处理:

    • 将S设为黑色
    • 将P设为红色
    • 对P进行左旋,得到情景2.1.2.3
    • 进行情景2.1.2.3的处理

    图15

    情景2.1.2:
    替换结点的兄弟结点是黑结点
    当兄弟结点为黑时,其父结点和子结点的具体颜色也无法确定(如果也不考虑自底向上的情况,子结点非红即为叶子结点Nil,Nil结点为黑结点),此时又得考虑多种子情景。

    情景2.1.2.1:
    替换结点的兄弟结点的右子结点是红结点,左子结点任意颜色
    即将删除的左子树的一个黑色结点,显然左子树的黑色结点少1了,然而右子树又有红色结点,那么我们直接向右子树“借”个红结点来补充黑结点就好啦,此时肯定需要用旋转处理了。如图16所示。
    处理:

    • 将S的颜色设为P的颜色
    • 将P设为黑色
    • 将SR设为黑色
    • 对P进行左旋

    图16

    平衡后的图怎么不满足红黑树的性质?前文提醒过,R是即将替换的,它还参与树的自平衡,平衡后再替换到删除结点的位置,所以R最终可以看作是删除的。另外图15是考虑到第一次替换自底向上处理的情况,如果只考虑第一次替换的情况,根据红黑树性质,SL肯定是红色或为Nil,所以最终结果树是平衡的。如果是自底向上处理的情况,同样,每棵子树都保持平衡状态,最终整棵树肯定是平衡的。后续的情景同理,不做过多说明了。

    情景2.1.2.2:
    替换结点的兄弟结点的右子结点为黑结点,左子结点为红结点
    兄弟结点所在的子树有红结点,我们总是可以向兄弟子树借个红结点过来,显然该情景可以转换为情景2.1.2.1。如图17所示。
    处理:

    • 将S设为红色
    • 将SL设为黑色
    • 对S进行右旋,得到情景2.1.2.1
    • 进行情景2.1.2.1的处理

     

    图17

    删除情景2.1.2.3:
    替换结点的兄弟结点的子结点都为黑结点
    好了,此次兄弟子树都没红结点“借”了,兄弟帮忙不了,找父母呗,这种情景我们把兄弟结点设为红色,再把父结点当作替代结点,自底向上处理,去找父结点的兄弟结点去“借”。但为什么需要把兄弟结点设为红色呢?显然是为了在P所在的子树中保证平衡(R即将删除,少了一个黑色结点,子树也需要少一个),后续的平衡工作交给父辈们考虑了,还是那句,当每棵子树都保持平衡时,最终整棵总是平衡的。
    处理:

    • 将S设为红色
    • 把P作为新的替换结点
    • 重新进行删除结点情景处理

    图18

    删除情景2.2:
    替换结点是其父结点的右子结点
    好啦,右边的操作也是方向相反,不做过多说明了,相信理解了删除情景2.1后,肯定可以理解2.2。

    删除情景2.2.1:
    替换结点的兄弟结点是红结点
    处理:

    • 将S设为黑色
    • 将P设为红色
    • 对P进行右旋,得到情景2.2.2.3
    • 进行情景2.2.2.3的处理

    图19

    删除情景2.2.2:
    替换结点的兄弟结点是黑结点

    删除情景2.2.2.1:
    替换结点的兄弟结点的左子结点是红结点,右子结点任意颜色
    处理:

    • 将S的颜色设为P的颜色
    • 将P设为黑色
    • 将SL设为黑色
    • 对P进行右旋

    图20

    删除情景2.2.2.2:
    替换结点的兄弟结点的左子结点为黑结点,右子结点为红结点
    处理:

    • 将S设为红色
    • 将SR设为黑色
    • 对S进行左旋,得到情景2.2.2.1
    • 进行情景2.2.2.1的处理

    图21

    删除情景2.2.2.3:
    替换结点的兄弟结点的子结点都为黑结点
    处理:

    • 将S设为红色
    • 把P作为新的替换结点
    • 重新进行删除结点情景处理

     图22

    综上,红黑树删除后自平衡的处理可以总结为:

    • 自己能搞定的自消化(情景1)
    • 自己不能搞定的叫兄弟帮忙(除了情景1、情景2.1.2.3和情景2.2.2.3)
    • 兄弟都帮忙不了的,通过父母,找远方亲戚(情景2.1.2.3和情景2.2.2.3)

    哈哈,是不是跟现实中很像,当我们有困难时,首先先自己解决,自己无力了总兄弟姐妹帮忙,如果连兄弟姐妹都帮不上,再去找远方的亲戚了。这里记忆应该会好记点~

  • 相关阅读:
    查询SGA,PGA pool 内存分配情况
    为2229岁的人解释一下什么叫工作
    关于log的一些脚本
    关于ARM公司的cortex系列
    git reset 小结
    git push 小结
    git push 小结
    关于ubuntu的aptget 包
    TTL接口 液晶屏 与 LVDS接口 液晶屏的 区别
    git reset 小结
  • 原文地址:https://www.cnblogs.com/nananana/p/10434549.html
Copyright © 2020-2023  润新知