• 算法实战:亲自写红黑树之四 插入insert的平衡


            本文承接自:

            算法实战:亲自写红黑树之一-CSDN博客

            算法实战:亲自写红黑树之二 完整代码-CSDN博客

            算法实战:亲自写红黑树之三 算法详解-CSDN博客

    目录

    一、入口

    二、普通二叉树插入

    三、插入后的平衡

    四、算法解惑


    一、入口

            入口点仿照stl:

    1. //如果second为false则已经存在,发生了覆盖,用GetOldValue获得被覆盖的值
    2. pairbool> insert(T_DATA const& data)
    3. {
    4. T_COMP comp;
    5. return insert(data, comp);
    6. }
    7. pairbool> insert(T_DATA const& data, T_COMP& comp)
    8. {
    9. m_OldValueSeted = false;//清除被覆盖对象的有效标志
    10. pairbool> ret;
    11. ret.first = end();
    12. ret.second = false;
    13. if (tree_head->free_head < 0 && m_array.Capacity() <= m_array.Size())
    14. {
    15. thelog << "超出容量限制" << ende;
    16. return ret;
    17. }
    18. try
    19. {
    20. ret = _insert(data, comp);
    21. }
    22. catch (exception& e)
    23. {
    24. thelog << e.what() << ende;
    25. }
    26. //thelog<<"insert ret "<
    27. return ret;
    28. }
    29. //返回被覆盖的值,如果最近的操作没有发生覆盖则false
    30. bool GetOldValue(T_DATA& ret)const
    31. {
    32. if (m_OldValueSeted)
    33. {
    34. ret = m_OldValue;
    35. return true;
    36. }
    37. else
    38. {
    39. return false;
    40. }
    41. }

            入口点首先处理了需要扩展容量的情形,这个扩展指的是树的删除链表为空,需要从底层数组申请空间的情形。

            我的实际应用场景更复杂些,如果底层数组也满了,就会再申请一块共享内存,由于两块共享内存地址地址无法连续,我用了一个地址映射表,从索引到数据要经过查表转换,这就是为什么我必须把底层操作抽象出来的原因。

            根据实际需要增加了GetOldValue函数,用来返回被覆盖掉的值。这个功能到底应不应该存在,见仁见智。

            T_COMP不理解就无视它,知道默认就是用“<”就可以了。

    二、普通二叉树插入

            普通插入很简单,由一组“_insert()”函数实现,前导“_”数量不同,表示不同的调用级别。

            普通插入后由_RB_insert_Balance(position)函数完成平衡。如果忽略这一句就是普通插入。

            如果是覆盖,则只替换数据,树结构不变,不需要平衡。

    三、插入后的平衡

            代码是比较简单的:

    1. void _RB_insert_Balance(T_SHM_SIZE x)
    2. {
    3. T_SHM_SIZE p = TREE_NODE::at(x).hParent;
    4. if (!_isRed(p))return;
    5. //连续红,需要调整
    6. bool isLeft = (x == TREE_NODE::at(p).hLeft);
    7. T_SHM_SIZE g = TREE_NODE::at(p).hParent;
    8. bool isL = (p == TREE_NODE::at(g).hLeft);
    9. T_SHM_SIZE u = (isL ? TREE_NODE::at(g).hRight : TREE_NODE::at(g).hLeft);
    10. if (_isRed(u))
    11. {
    12. //u为红只需要染色,然后递归
    13. TREE_NODE::at(p).bColorRed = false;
    14. TREE_NODE::at(u).bColorRed = false;
    15. TREE_NODE::at(g).bColorRed = true;
    16. _RB_insert_Balance(g);
    17. }
    18. else
    19. {
    20. if (isL)
    21. {
    22. if (isLeft)
    23. {//LL
    24. _RRotate(g);
    25. _exchage_color(p, g);
    26. }
    27. else
    28. {//LR
    29. _LRotate(p);
    30. _RRotate(g);
    31. _exchage_color(x, g);
    32. }
    33. }
    34. else
    35. {
    36. if (isLeft)
    37. {//RL
    38. _RRotate(p);
    39. _LRotate(g);
    40. _exchage_color(x, g);
    41. }
    42. else
    43. {//RR
    44. _LRotate(g);
    45. _exchage_color(p, g);
    46. }
    47. }
    48. }
    49. }

            x 插入的新节点

            p x的父节点

            u x的叔叔,也就是p的兄弟

            g x的祖父,也就是p和u的父节点

            _isRed(h) 判断节点是不是红色

             _LRotate(h) 左旋,只改变父子关系,颜色和数据不变

            _RRotate(h) 右旋,只改变父子关系,颜色和数据不变

            _exchage_color(h1,h2) 交换两个节点的颜色

    四、算法解惑

            这个算法的原则其实很简单,不是双红不用处理,是双红则:

    1. 如果上一层两个都是红,则g必然是黑(红黑树规则),于是就将上一层两个都变成黑、g变成红,于是下面符合规则了,但是g变成了红,可能造成新的双红,于是再对g做平衡。这个过程可能一直递归到顶。
    2. u是黑,挪一个红过去。具体挪法分四种情形。听起来也不简单?

            其实吧,所谓“u是黑”,意思是u是空啊,u不是空的话g左右两边深度就不一样了,违反红黑树规则。所以双红其实就是g下面只有一个红节点,新的节点有挂在了这个红节点下面,也就是“g-红-红”,只需要重新布局,改成g下面一边一个就行了。

            而第一种情形的“u是红”呢,就是g下面两个都是红,新的节点又挂在其中一个下面,也就是“g-红红-红”,不可能再有别的黑色数据节点(不是空)存在。

            是不是豁然开朗?

    (这里是结束)

  • 相关阅读:
    Unity中PICO中手柄按键返回值
    解析java中的String类中的常用方法(三)
    有了PMP证书,还用考CSPM吗?
    viewerjs -v 11 动态获取图片(ajax),以及重复初始化问题。
    JAVAWeb1:登录页面
    【FPGA+sin】基于DDS(直接数字合成)的正弦信号发生器模块FPGA实现
    FPGA基于spi的flash读写
    镜面不锈钢氮气柜主要功能和应用领域介绍
    DS@命题公式等值演算@常用等值式模式
    oracle 如何使用脚本实现访问控制(无需额外插件)
  • 原文地址:https://blog.csdn.net/2301_77171572/article/details/134433889