SGI-STL简记(七)-关联容器(set、map、multiset、multimap)

stl_set.h :
    set:有序关联容器,值类型和键类型为同一个,且各个容器元素唯一,插入或删除在线性时间内完成,此外插入等操作不会影响迭代器失效的情况;
    
    set:关联容器set模板类,其参数分别为_Key、_Compare、_Alloc,对应键值类型、比较函数、内存分配器;
    此外提供了特化版本,其比较函数使用的是less<_Key>,分配器使用宏__STL_DEFAULT_ALLOCATOR作为默认分配器;可通过调整宏设置,故而可使用allocator< T >或alloc(malloc_alloc(即__malloc_alloc_template<0>)
    或__default_alloc_template<__NODE_ALLOCATOR_THREADS, 0>)作为默认的内存分配器;
    内部底层使用一个数据成员_M_t,其类型为RB_Tree红黑树结构来实现set,RB_Tree不仅被用于set,还被用于map、multiset、multimap的底层实现;
    set内部重声明类型key_type、value_type、key_compare、value_compare,其中key_type、value_type相同均为_Key,key_compare、value_compare则均为_Compare;
    此外还有其他如指针、迭代器、引用、以及其他常规的声明类型;
    因内部使用红黑树实现,故set对外提供的多个接口均直接或间接使用数据成员_M_t的相同或相似的接口,如begin、end、size等;
    着重看看swap函数内部直接交换_M_T的swap(事实上只是简单地交换红黑树的部分指针、缓冲区即可)、此外operator==、operator<等重载操作符也是_M_T的比较;
    
stl_map.h :
    map:有序关联容器,分为值类型和键类型,其中值类型value_type为pair<const _Key, _Tp>且容器元素键key唯一,此外插入等操作不会影响迭代器失效的情况;
    map:关联容器map模板类,模板参数类似set,增加了data_type值类型、mapped_type类型均声明明_Tp,区分key_type、value_type,data_type,即data_type为value_type的second类型,而key_type为
    value_type的first类型;
    此外提供了特化版本,类似于set;
    对外提供的接口和set类似,且底层实现均基于其数据成员_M_t红黑树;
    
stl_multiset.h :
    同set类似为有序关联容器,但可以保存相同的元素;
    
stl_multimap.h :
    同map类似为有序关联容器,但可以保存相同key的元素;

stl_tree.h :
    _Rb_tree:红黑树结构容器,一种自平衡二叉查找树,可在log(n)时间内实现插入、删除、查找操作,主要是实现高效的查找操作,其一般作为set、multiset、map等关联容器底层实现;
    (事实上意味着树中最左端的节点值最小,最右端节点的值为最大,即左、中、右节点的方式节点值依次增大);
    红黑树性质:
        1. 节点是红色或黑色;
        2. 根节点root是黑色;
        3. 每个叶子节点(空节点)为黑色;
        4. 每个红色节点的两个子节点是黑色的;
        5. 从任意一个节点到其每个叶子节点的所有路径都包含相同的数目的黑色节点;
    
    _Rb_tree_Color_type:bool类型的重声明,且定义了两个常全局变量_S_rb_tree_red、_S_rb_tree_black分别设置值为false、true,以区分表示红黑树中的红节点、黑节点;
    
    _Rb_tree_node_base:红黑树节点基类;
    数据成员:
        _M_color:当前节点颜色,即黑节点或红节点;
        _M_parent:指向当前节点的父节点的指针;
        _M_left:指向当前节点的左子节点的指针;
        _M_right:指向当前节点的右子节点的指针;
    成员函数:
        _S_minimum:静态成员函数,返回某个指定节点下的最左端子节点;
        _S_maximum:静态成员函数,返回某个指定节点下的最右端子节点;
    
    _Rb_tree_node:红黑树节点模板类,继承于_Rb_tree_node_base,模板参数为值域类型;此外内部重声明了当前节点类型指针_Link_type以及一个成员变量_M_value_field;
    
    _Rb_tree_base_iterator:红黑树迭代器基类,内部重声明节点指针类型、bidirectional_iterator_tag双向迭代器标识,也就意味着基于此的容器均支持双向迭代;
    数据成员:
        _M_node:迭代器指向的某个节点的指针;
    成员函数:
        _M_increment:迭代器移向下一个节点;
            移动策略为:
                1. 若当前_M_node节点的右节点不为空节点,则取其右节点下的最左端的且不为空的子节点作为_M_node节点的下一个节点;
                2. 若_M_node节点的右节点为空节点且_M_node节点为父节点的右节点则取当前节点的父节点作为当前节点、父节点的父节点作为父节点以此依次遍历取得第一次当前父节点的右节点不为当前节点时停止,取父节点作为_M_node节点下一个节点;
                3. 若_M_node节点的右节点为空节点且_M_node节点不为父节点的右节点则取父节点作为_M_node节点的下一个节点;
                其中2,3可以作为一个情况处理,即当前节点的右节点为空节点,则取第一次其父节点或祖父节点的右节点不为当前节点时,父节点即为_M_node节点的下一个节点;
        _M_decrement:迭代器移向上一个节点;
            移动策略为:
                1. 若当前_M_node节点为黑节点且当前节点的父节点的父节点为当前节点,则取当前节点的右子节点作为当前_M_node节点的上一个节点(有点儿不太懂,可能当前红黑树不同于一般的红黑树);
                2. 若当前节点的左子节点不为空,则取其左节点下的最右端的节点作为_M_node节点的上一节点;
                3. 对于其他情况,取当前节点的父节点,若父节点的左节点为当前节点,则取当前节点的父节点作为当前节点,父节点的父节点作为父节点以此以此遍历取得第一次当前父节点的左节点不为当前节点时停止,取父节点作为_M_node节点上一个节点;
                若父节点的左节点不为当前节点,则父节点作为_M_node节点的上一节点;
    
    _Rb_tree_iterator:红黑树迭代器模板类,继承于_Rb_tree_base_iterator,模板参数分别为值类型_Value、引用类型_Ref、以及指针类型_Ptr;
    内部重声明了值类型、引用类型、指针类型以及迭代器类型、_Link_type链接类型;
    重载版本的构造函数,提供以默认构造、_Link_type参数以及其他迭代器构造方式以初始化_M_node;
    重载的解引用operator*以及operator->获取_Rb_tree_node类中的_M_value_field值或地址;
    重载的operator++、operator++(int)、operator--、operator--(int)分别调用_M_increment、_M_decrement实现迭代器前向和后向移动;
    此外提供全局友元重载operator==、operator!=比较操作符版本,内部直接比较_M_node(也即比较颜色、节点各指针值等);
    
    内置的核心辅助工具函数:
        _Rb_tree_rotate_left:红黑树的左旋转操作;参数x、root分别为对应指定旋转节点和根节点;
        旋转步骤为:
            1. 取得旋转节点x的右节点y,并调整旋转节点x的右节点指针指向节点y的左节点;
            2. 若y节点的左节点不为空,则y节点的左节点指向的父节点指针调整指向旋转节点x;
            3. 调整y节点的父节点指针指向x节点的父节点;
            4. 若旋转节点x为即为root根节点,则调整根节点为y节点;
               否则若x节点为其父节点下的左节点,则调整x节点的父节点的指向左节点指针指向y节点;
               否则调整x节点的父节点的指向右节点指针指向y节点;
            5. 调整y节点的指向左节点的指针指向x节点,调整x节点的父节点指针指向y节点。
            
        _Rb_tree_rotate_right:红黑树的右旋转操作;参数x、root分别为对应指定旋转节点和根节点;
        旋转步骤为:
            1. 取得旋转节点x的左节点y,并调整旋转节点x的左节点指针指向节点y的右节点;
            2. 若y节点的右节点不为空,则y节点的右节点指向的父节点指针调整指向旋转节点x;
            3. 调整y节点的父节点指针指向x节点的父节点;
            4. 若旋转节点x为即为root根节点,则调整根节点为y节点;
               否则若x节点为其父节点下的右节点,则调整x节点的父节点的指向右节点指针指向y节点;
               否则调整x节点的父节点的指向右节点指针指向y节点;
            5. 调整y节点的指向右节点的指针指向x节点,调整x节点的父节点指针指向y节点。
        基本上左旋和右旋操作过程基本一致,只需要处理好各个节点内各指针指向调整即可;
    
        _Rb_tree_rebalance:遍历调整红黑树平衡操作(内部通过旋转节点以及修改节点颜色达到平衡状态,满足红黑树的几个性质);参数x、root分别为对应指定调整节点和根节点;
        操作步骤为:
            0. 设置当前调整节点颜色为红色;
            1. 判断当前调整节点x是否不为root节点且调整节点的父节点颜色为红色,则执行后面步骤,否则结束平衡调整;
            2. 若调整节点x的父节点为调整节点x的父节点的父节点下的左节点;
                取调整节点x的父节点的父节点下的右节点y;
                    若节点y不为空且y节点颜色位红色,则调整x节点的父节点颜色为黑色,y节点颜色为黑色,x节点的父节点的父节点颜色为红色,并调整x节点为x节点的父节点的父节点作为新的一轮的调整节点,重新继续执行步骤1;
                    否则判断x节点是否为x节点的父节点下的右节点,
                        1). 若是则调整x节点为x节点的父节点作为新的一轮的调整节点并调用_Rb_tree_rotate_left左旋转调整新的x节点;
                        2). 调整x节点的父节点颜色位黑色,调整x节点的父节点的父节点颜色为红色,调用_Rb_tree_rotate_right右旋转调整x节点的父节点的父节点,此后重新继续执行步骤1;
                否则取调整节点x的父节点的父节点下的左节点y;
                    若节点y不为空且y节点颜色位红色,则调整x节点的父节点颜色为黑色,y节点颜色为黑色,x节点的父节点的父节点颜色为红色,并调整x节点为x节点的父节点的父节点作为新的一轮的调整节点,重新继续执行步骤1;
                    否则判断x节点是否为x节点的父节点下的左节点,
                        1). 若是则调整x节点为x节点的父节点作为新的一轮的调整节点,并调用_Rb_tree_rotate_right右旋转调整新的x节点;
                        2). 调整x节点的父节点颜色位黑色,调整x节点的父节点的父节点颜色为红色,调用_Rb_tree_rotate_left左旋转调整x节点的父节点的父节点,此后重新继续执行步骤1;
            3.     调整root根节点颜色位黑色。
            
        _Rb_tree_rebalance_for_erase:    移除某个节点后并重新调整红黑树平衡(事实上只是从红黑树中剔除并未释放该移除节点内存空间),参数z、root、leftmost、rightmost分别表示被移除的节点、根节点、最左端节点、最右端节点;
        因移除节点后需重新调整树至平衡,则调节过程前先找到移除节点后的调整节点以及调整节点的父节点;
        操作步骤(内部细节处理稍微有点儿复杂,此处暂只是罗列大概内容):
            0. 用y保存指向移除z节点的临时副本,用以找到替换z节点位置的某个节点;
            1. 获取移除z节点后的调整节点x:
                若y节点的左节点为空,则调整节点x取y节点的右节点(也有可能x为空);
                否则判断y节点的右节点是否为空;
                    若为空则调整节点x取y节点的左节点(此时x不可能为空);
                    否则y节点调整为y节点的右节点并循环找到y节点下的最左端节点,且调整节点x取此时y节点的右节点(此时的x也有可能为空);
                此时x节点可能为空,y节点不可能为空;
            2. 获取到新的节点y以替换z节点位置以及获取调整节点x的父节点__x_parent(内部需调整各个节点内部各指针指向,甚至可能调整root、leftmost、rightmost指向)(保证左中右,小中大的形式);
            3. 从调整节点x开始重新调整红黑树至平衡;
    
    _Rb_tree_alloc_base:红黑树内存分配基类模板;模板参数分别为数据类型_Tp,分配器类型_Allo,以及一个bool标识_S_instanceless(用于区分是否为标准分配器或SGI分配器);
    数据成员:
        _M_header:红黑树的头节点,_M_header的指向的父节点指针为红黑树的根root节点,_M_header的左右节点指针分别指向红黑树的最左端节点、最右端节点并辅助于容器迭代器,根root节点的指向的父节点指针为_M_header;
        _M_node_allocator:分配器对象;
    成员函数:
        构造函数:分配器引用allocator_type类型以初始化_M_node_allocator;
        get_allocator:获取分配器对象_M_node_allocator;
        _M_get_node:通过分配器对象_M_node_allocator分配大小为1个的元素类型大小内存空间;
        _M_put_node:释放指定数据元素类型指针地址大小为1个数据元素类型大小的内存空间;
    此外还提供特化版本_Rb_tree_alloc_base<_Tp, _Alloc, true>,该分配模板基类内部不再使用分配器对象,而是直接使用simple_alloc的静态成员函数进行分配管理;
    
    _Rb_tree_base:_Rb_tree基类,继承于_Rb_tree_alloc_base,其基类的模板参数_S_instanceless则通过_Alloc_traits萃取获得的_S_instanceless来初始化;
        构造函数:通过分配器类型对象初始化;此外通过基类的_M_get_node接口分配一个节点大小的缓冲区以初始化_M_header;
        析构函数:调用_M_put_node释放_M_header所指向的节点缓冲区;
    
    _Rb_tree:红黑树模板类,保护继承于_Rb_tree_base,模板参数中的分配器使用宏__STL_DEFAULT_ALLOCATOR作为默认分配器;可通过调整宏设置,故而可使用allocator< T >或alloc
        (malloc_alloc(即__malloc_alloc_template<0>)或__default_alloc_template<__NODE_ALLOCATOR_THREADS, 0>)作为默认的内存分配器;
        其他的模板参数_Key、_Value、_KeyOfValue、_Compare分别为键类型、值类型、取值的键类型,以及比较函数类型;
        此外内部重声明了多种类型如key_type、value_type、指针、引用、迭代器等类型;
        数据成员:
            _M_node_count:节点计数,用以保存当前红黑树节点个数(数据元素个数);
            _M_key_compare:键比较操作函数;
        成员函数:
            get_allocator:重写基类函数,获取基类的分配器对象;
            构造函数:多种重载版本的构造函数,包括默认构造、比较函数、分配器类型对象以初始化_M_node_count、_M_key_compare此外调用_M_empty_initialize初始化红黑树;
            _M_empty_initialize:初始化_M_header节点的颜色、父节点、左节点、右节点,内部调用_S_color设置节点的颜色为红色以区分root节点,_M_leftmost、_M_rightmost内置函数
            分别为容器最左端节点、最右端节点指针,其初始化节点指向_M_header(指向自己头节),_M_root设置_M_header的父节点指向空;事实上有很多以_S_XXX开头的函数均为内置静态函数以提取相应节点的域;
            如:_S_left即取节点x的左节点,_S_value取节点x的值域;如当第一次调用insert_unique插入元素时,会创建元素节点并作为红黑树的root,此时调整_M_header的父节点指针指向该root节点,同样的_M_header
            的左右子节点指针指向该root节点,而该root节点的父节点指针指向_M_header,因_M_header的颜色和root颜色不一样,root节点颜色始终为黑色而_M_header为红色,这样既可达到区分此两个节点的目的,
            若此后再调用insert_unique插入元素时,则新插入节点将为root节点的子节点或者作为新的root节点,再调整_M_header、root、新节点的内部指针指向;
            拷贝构造函数:若参数容器为空,则直接调用_M_empty_initialize即可否则调用_S_color设置_M_header节点的颜色为红色,_M_copy初始化拷贝所有节点并设置_M_header指向的父节点(即根节点);此后调整
            _M_header的左右节点指针指向最左端节点、最右端节点,记录当前节点_M_node_count大小;
            _M_copy:拷贝源容器中的所有节点至当前容器,内部递归调用_M_clone_node、_M_copy创建拷贝节点(从根节点出发,按照根右左依次递归拷贝);
            _M_create_node:申请缓冲区创建值为val的节点,内部调用_M_get_node分配一个节点缓冲区,此后调用construct初始化缓冲区节点值域为val;
            _M_clone_node:拷贝节点,内部调用_M_create_node拷贝值为val的节点,此外除颜色和值域被拷贝外,左右节点指针设置为空;
            析构函数:调用clear销毁各个节点;
            clear:销毁容器所有节点,内部先调用_M_erase移除从root节点的所有节点;此后调用_M_leftmost、_M_root、_M_rightmost设置_M_header节点指针指向;
            _M_erase:遍历移除root节点下的所有节点(内部按照右根左的方式递归遍历并调用destroy_node移除各节点);
            destroy_node:销毁节点,内部调用destroy销毁节点值域析构函数,此后调用_M_put_node释放该节点缓冲区内存空间;
            key_comp:返回键比较函数_M_key_compare;
            begin:返回容器首迭代器(实际上为_M_header的_M_left指针指向的节点,构造的迭代器)(也是root树下最左端节点位置);
            end:返回_M_header节点构造的迭代器(因_M_header的_M_right指针指向为最后一个节点元素,则最后一个节点的下一个节点即root节点的父节点,刚好为_M_header节点);
            rbegin、rend:借助于end、begin构造翻转迭代器;
            empty、size:借助于_M_node_count节点计数返回节点数或判断容器是否为空;
            max_size:返回容器最大容量大小(返回size_type(-1),而不是size_type(-1)/sizeof(T));
            swap:交换两个容器元素(事实上只是简单的交换_M_header、_M_node_count、_M_key_compare);
            insert_unique:提供多个重载版本,其中一个为插入指定的值val,在指定迭代器下插入值val以及插入输入迭代器范围的数据元素版本;
                插入指定的值val版本:从根节点起while遍历_M_key_compare比较插入值与各个节点值大小,若插入值小于节点值则再比较节点的左节点,否则比较节点的右节点直到节点的左右节点为空为止;
                对于最后找到的节点,若其节点值小于插入值;则再次比较该节点是否与首迭代器相同,若相同则调用_M_insert插入该值并返回迭代器、bool为true的pair对,否则取找到的节点的上一个节点;
                再次比较该节点是否与首迭代器相同,若是则调用_M_insert插入该值并返回pair迭代器、bool值对,否则取找到的节点的上一个节点;
                最后判断找到的节点的值是否小于插入值,若是则调用_M_insert插入该值并返回迭代器、bool值true的pair对;否则返回迭代器、bool值false的pair对;
                其他版本类似于以上操作;
            _M_insert:在指定节点处插入值val,内部调用_M_create_node创建节点并调整指定节点处各节点指针指向,此后调用_Rb_tree_rebalance重新调整平衡;
            insert_equal:也提供了多个重载版本,但基本上底层调用_M_insert实现插入操作,相对于insert_unique,该实现可插入容器元素相等的值,基于此可满足如set与multiset或者map与multimap间
            保存不同或相同元素时调用不同的实现,即multiset和multimap插入元素时调用的是insert_equal,而map和set则调用insert_unique;
            erase:提供了多个重载版本,如移除指定位置的节点、移除迭代器范围的节点等;每移除一个节点都会调用_Rb_tree_rebalance_for_erase重新调整平衡,故比较耗时;
            其他接口函数如:find、lower_bound、upper_bound、equal_range、count均采用从根节点开始比较各个节点,类似于二分查找的形式,均可在log(n)时间复杂度下快速找到邻近值节点或相等的节点;
            __rb_verify、__black_count:主要用于其他用途,如调试、统计黑色节点计数等;
            __black_count递归遍历指定节点开始的以及该节点的父节点直到root节点为止时的黑色节点计数;
            __rb_verify内部按照红黑树的几个性质判断,是否当前结构满足,如判断节点为红色时两个左右节点是否为红色节点,判断节点值小于右节点值而大于左节点值,判断各通路是否黑色节点数相同,
            判断树中最左端节点是否为根节点中最小值的节点;最端节点是否为根节点中最大值的节点等。
            
        基本上红黑树中插入、移除、重新调整平衡实现方面相对比较复杂且较为耗时,但查找相关操作则比较高效,其亦被用于map、multiset、multimap的底层实现;
        map、set间使用红黑树作为底层,
        1. 那么上层的区别为map的值类型value_type为pair<const Key, Tp>且容器元素键唯一,而set的值类型value_type为key_type;
        2. 红黑树的比较函数_M_key_compare,map下为提取的key,set下为key(也是valye_type),故可以统一为比较key,通过key确定是否允许插入当前值类型value_type的值;
        3. set的key_compare和value_compare比较函数相同,均为传入的容器模板参数_Compare;map下的key_compare为传入的容器模板参数_Compare,而value_compare则为一个内嵌的类,该类继承于
        binary_function,value_compare内部数据成员comp为比较函数,该比较函数被构造初始化为key_compare,此外重载的operator()内部调用comp(__x.first, __y.first)实现键比较;
        4. 红黑树的模板参数_KeyOfValue当为set时其为_Identity<value_type>,而为map时为_Select1st<value_type>,_Select1st继承于unary_function,内部重载了operator()返回pair对的first值;
        _Identity继承于unary_function,内部重载了operator(),直接返回值容器元素值x,其目的是为了萃取容器元素中value_type值中的key,以支持_M_key_compare比较目的实现容器元素唯一性;
        5. map重载了operator[],内部调用lower_bound获取插入值可插入的迭代器位置下边界位置,若插入值已存在则直接返回value_type的second值,否则调用insert插入到该下边界位置处并返回插入值value_type的second值;
        set未重载operator[];
        6. 除以上几点不同之外,其他的如重载的比较操作函数、外部调用的成员函数均大概一致;
        7. multiset、multimap相对以上两者的主要的区别:在于insert操作,后两者内部调用的是insert_equal,而set和map调用的是insert_unique;以区分是否容器保存相同元素的目的;
        此外因multiset或multimap可以保存相同的元素,故某些涉及到查找操作时是针对第一个匹配的元素,此外同map和set一样如重载的比较操作函数、外部调用的成员函数均大概一致;
    
    关联容器中set、map、multimap、multiset的实现核心为底层的红黑树_Rb_tree,而_Rb_tree的核心为插入、移除、重新调整平衡等几个比较复杂的操作.

 

posted @ 2019-10-10 12:36  浩月星空  阅读(283)  评论(0编辑  收藏  举报