STL学习笔记

这篇笔记暂时只是一部分,后续的部分会慢慢贴出来。有错误之处还望大神指教

1,容器
(1)vector
    vector要求<vector>的头文件包含,实际的实现是在<stl_vector.h>中。
    vector的初始化方式:
    1,直接初始化空:
        vector():start(0),end(0),end_of_storage(0){};
        ex: vector<int> vec;
    2,初始化并赋值
        vector<int/double/long/decimal/float etc.> vec(【int/long】num,【value】);
        表示初始化vector含有一定num数量的元素,后面的value指的是这些元素同时赋予相应的值,此时的value是一个const常量。
    3,直接初始化个数
        vector<int> vec(num);    
    这样就初始化了一个一定num数量的vector
    vector的操作:
        *.front();//返回 *begin();
        *.back();//返回*(end()-1);
        *.push_back(const T& x);//最常用的尾部插入操作;
        *.pop_back();//删除最后一个元素
        *.erase(iterator position);//删除迭代器指向对象,然后返回指向下一个元素的迭代器对象;
        *.resize(size_type new_size,const T& x);//重新调整vector的大小,如果new_size大于原来的空间,就在保留原来数据的基础上扩展相应大小的空间并在新拓展的空间内赋予x的值;如果new_size的大小小于原来的空间,就从begin()开始erase这new_size个空间的值,剩下的保持不变。
        *.resize(size_type new_size);//调用上面的函数;
        *.clear();//擦除begin到end的所有的值;
    vector的属性:
        begin()和end()是最常用的vector的属性;
        这里要注意一个问题,vector的capacity()和end()是不一样的。capacity是指的所有的空间,包括未赋值的空间,end()则指的是所有已经赋值的空间的末尾。
        还有,vector的迭代器在进行插入/删除之后可能失效,这一点非常重要。
    vector增加空间大小并不是另开辟一块大于等于已使用空间大小的空间,而是另找一个大于等于原来空间的2倍的空间来存放元素。在找到这个空间之后,将原来的数据拷贝过去。所以,vector进行多数据的频繁的插入和删除操作时候效率更低一些。这就是造成迭代器失效的根本原因所在。但是vector进行查询的效率更高。
 
2,list
    STL的list不同于普通的list,它直接就是一个双向链表。list的节点定义是这样的:
template <class T>
struct __list_node{
        typedef void * void pointer;
        void_pointer prev;
        void_pointer next;
        T  data;
};
所以list在这里是一个双向链表,而且是一个循环链表。用一个指针就能同时表示出链表的头和尾,因为有next和prev指针;
 
list的元素操作:
    push_front(const T& x);//从头部插入
    push_back(const T& x);//从尾部插入、
    erase(iterator position);//擦除制定位置的值
    pop_front(); 
    pop_back();
    clear();//清楚整个链表
    remove(const T& value);//将数值为value的所有元素移除,这个比较nice
    unique();//移除【连续而相同的】元素,只剩下一个,一定要满足相应的条件
    
    transfer(iterator position,iterator first,iterator last);//将first和last之间的所有元素(包含first,但不含last)移动到position之前;
    splice(iterator position, list & x);//将x这个list中的所有元素移动到position之前,这个操作是基于上面的transfer操作的;
    splice(iterator positon , list & x , iterator i);//将i所指的元素结合于position之前;
    
最后有 merge() , reverse() , sort()操作,这些操作明显是基于transfer()的。
    
    从list的一系列特性可以看出,list对于频繁增删的数据存储是有着明显的优势的,其各种元素操作的方法应有尽有,操作元素非常方便。这是相对于vector来讲的一个方面。此外,list的迭代器不会像vector那样很容易失效,只有在erase某个元素的时候,这个迭代器才可能失效。不过,相对于vector来讲,list在查询元素方面效率就比较低了,因为其本质上还是一个链表,需要遍历链表来查询。
 
3,deque
    deque是这样一个容器:其容器是可增长的,可push/pop_front()也可push/pop_back(),但是其在头端的push和pop的效率奇差。
    此外,deque的增长空间是连续的--在头端或者尾端插入元素的时候,如果超出了原来的空间,那么久分配新的连续空间接上原来的空间。实际上这是一个伪连续的空间,真正分配的新空间并不是连续的。之所以看起来是连续的,是因为每一个deque都有一个map来维持,map一般是一个512byte大小的空间,蛋疼的是这个空间也是变化的。这个map里面保存的就是一段段不连续空间的地址。就是这个map让deque看起来是空间连续的。
    这样就有一个问题,就是在对deque进行操作的时候,迭代器的复杂度非常高。虽然看起来deque拥有了vector和list的共同的优点,但是,其效率还是比较低下; 
    对元素的操作:
    deque有一些列对元素的操作,和list大同小异。鉴于效率的原因,可能我们并不需要考虑使用deque;
   
4,priority_queue
    其实就是一个只准从头出,从尾进的一个容器。而且从头出来的元素,要么是所有元素中最小的,要么是所有元素中最大的。这里就用的是最大顶堆和小顶堆来实现的。
所以这里一定要用到堆heap。
    heap最常用的就是在于堆排序里面。
    堆排的基础就是要构建大顶堆(max tree)和小顶堆(min tree),这里的堆是一个complete binary tree(完全二叉树)。
    大顶堆的特点就是父节点的值总是大于等于子节点的值,这样就保证了在整个堆中,根节点的值是最大的。每次取出根节点的值,然后将剩下的元素继续构建大顶堆,这样就完成了降序的排序。
    STL中,构建heap需要的是一个存放元素的vector,和一个构建heap的算法。(使用vector的原因是其可以动态改变大小)
    例如:见图
heap算法:
    1,构建heap(每个左子节点的序号都是2i,每个右子节点的序号都是2i+1,其相应的父节点的序号是i/2,以此来构建二叉树);
    2,维持heap;
        在每次插入或者pop() heap中的元素时(插入一定在末尾,然后再调整。pop一定在堆顶,然后再调整),可能heap的形态就要发生变化,此时就要重新调整heap到正常状态,使之符合大顶堆的特性。
        使用 _ajust_heap算法来维持大顶堆的形态。
    
5, RB-tree
    红黑树是一种特殊的数据结构,不同于BST和AVL,红黑树在除了具有以上两者的特点的同时,还具有不同颜色背景的特点。
    (1),RB-tree的根节点一定为黑色;
    (2),节点只有黑色和红色两个颜色;
    (3), 父子结点不能同时为红色;
    (4),任意结点到达NULL结点之任一路经中所包含的黑色结点的数量必须相同;
    红黑树在插入数据时,新增的结点一定是叶子结点,默认为红色。一般来说,插入数据都会破坏树的规则,所以必须使用旋转树形并调节结点的颜色;
    插入方式分为外侧插入和内侧插入,针对不同的插入方式,在调整树形的时候需要选择单旋转和双旋转来调整树形。
    RB-tree的结点定义如下:
    typedef  bool _rb_tree_color_type;
    const _rb_tree_color_type   _rb_tree_red = false; //red is 0
    const  _rb_tree_color_type  _rb_tree_black=true;//black is 1
 
    struct _rb_tree_node_base{
 typedef _rb_tree_color_type color_type;
 typedef _rb_tree_node_base *base_ptr;
 
 color_type color;//结点颜色,非红即黑
 base_ptr parent;//RB树的许多操作,必须知道父亲结点
 base_ptr left;
 base_ptr right;
 
 static base_ptr mimimum(base_ptr x){
  while(x->left!=0)
   x=x->left;
  return x;
 };
 static base_ptr maxmum(base_ptr x){
  while(x->right!=0)
   x=x->right;
  return x;
 };
 
 template <class Value>
 struct _rb_tree_node:public _rb_tree_node_base{
  typedef _rb_tree_node<Value>* link_type;
  Value value_field;//节点值
 };
};
 
RB-tree作为一个基础的容器,是实现其他容器的基础。基本的操作就是插入和查操作。
从上面的结点可以看出,RB-tree寻找最大值和最小值非常方便。
 
6,set
    set作为一个关联容器,保存的主要是键-值对,set中所有的元素会根据键值自动被排序,set的元素不像map一样可以同时拥有实值(value)和键值(key),set的键值就是实值,实值就是键值。所以set不允许有相同的键值。
    我们不能通过set的迭代器改变set的键值,因为set的键值和实值是一样的随意改变键值会破坏set的排列规则。set的iterator是一种const iterator。
    set是以RB-tree二叉搜索平衡树为基本的数据结构,因此在对其进行insert和erase操作时 iterator 不会失效,除了删除的那个迭代器。
注意:
    1,set的insert,是RB-tree的insert-unique()而不是insert-equal(),因此在向set中插入已有的数值的时候相当于没有对set采取任何操作。
    2,在对关联容器使用STL算法时候,最好采用容器自带的算法,例如find(),reverse()算法等。
 
7,map
    map是键-值对关联容器,map的值可以重复,但是键不能重复,必须保持键的唯一性。因为map是根据键值进行默认的递增排序的。
    map在这里类似于hash table,还有一种基于hash的 hash_map,这里的map是基于RB-tree的。
    根据map的定义可知map的定义方式:
    template <class key, class T
                        class compare=less<key>,  //缺省情况下默认为递增排序
                        class Alloc=alloc> //缺省
    由于map是基于RB-tree的,所以,其iterator 失效的方式和RB-tree是一样的。
 
8,multiset 
    multiset 于set特性都相同,不同之处在于multiset允许键值重复,所以其在插入数据时使用的是RB-tree的insert-equal()方法,而不是insert-unique()方法。
 
9,multimp
    multimap与map的特性完全相同,不同之处在于它允许键值重复,所以,与multiset一样,它使用的是insert-equal()方法。
 
注意:以上的set,map,multiset 和 multimap 使用的底层都是RB-tree,而RB-tree的底层是平衡二叉搜索树。因为平衡二叉搜索树在查询、插入和删除操作上的复杂度比较低。
 
10,hashtable
    哈希表是一个特殊的容器,主要是使用键-值的一一对应关系来解决查询和插入、删除的平均操作时间。在哈希表足够大的时候,元素的冲突必然不可避免。一般是采用线性探测和二次探测的方法。线性探测会产生主集团的影响,二次探测虽然消除了主集团的影响,但是会产生次集团的影响。
    在STL中使用开链来解决hashtable中的元素冲突问题。
    开链就是用一个list来存放hashtable空间的指引,指示哪里有空闲的空间可以插入。这个list会根据‘集团’的大小改变list中的元素位置指示。
    STL中的hashtbale 比较复杂,初始化的方式也很麻烦,具体的定义方式如下:
    template <class Value, class key , class HashFcn,
                        class ExtractKey , class EqualKey ,
                        class Alloc>]
    其中,Value--结点的实值类型
               Key--结点的键值类型
              HashFcn--Hash function的函数类型
              ExtractKey--从结点中取出键值的方法(函数或仿函数)
              EqualKey--判断键值相同与否的方法(函数或仿函数)
              Alloc--空间配置器,缺省使用std::alloc
一个初始化hashtable的例子:
        hashtable<int,
   int,
   hash<int>,
   identity<int>,
   equal_to<int>,
   alloc>
   myhashtable<50,hash<int>(),equal_to<int>());//指定保留50个buckets
 
11,hashtable的派生
    基于hashtable的派生容器有hash_set , hash_map , hash_multiset , hash_multimap
    这些容器同非hash的有相同的方法,但是有不同的实现方式。
    
 
 
 
 

转载于:https://www.cnblogs.com/kzcdqbz/p/4678970.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值