STL容器总结

  通用的容器分为三类:顺序性容器、关联式容器和容器适配器。

一、顺序性容器

  顺序性容器是一种各元素之间有顺序关系的线性表,除非用插入、删除的操作改变位置,否则元素在容器中的位置与元素本身没有关系,只与操作的时间和地点相关(时间:什么时候添加的元素,地点:元素添加到了那个位置);常用的顺序性容器有:vector、deque、list。

  • Vector

  vector是一个线性顺序结构,相当于数组。与数组不一样的地方是可以动态的扩容,其他特性与数组完全一样。它的扩容规则是当元素个数超出其capacity()时,会重新申请一块内存,其大小是之前capacity()的两倍,这种扩容操作很耗费时间,效率低下。所以最好在初始化申请其空间大小,这样效率比较高。

  由于vector在内存中是连续的,而且支持自动扩容,所以显而易见,其在后部push_back()、pop_back()元素的动态操作是很高效的,但是在内部进行增删的动态效率非常低,所以尽量避免这样操作。

  vector的初始化:

    vector<int> a(10);                           //定义10个整型元素的向量,没有定义其初值,其值是不确定的
    vector<int> a(10, 0);                        //定义10个整型元素的向量,初值设置为0
    vector<int> a(b);                            //利用容器b来初始化a,整体复制
    vector<int> a(b.begin(), b.begin()+ 5);      //使用容器b的前五个元素来构造a

  vector的常用操作:

    a[i];                     //返回第i个元素
    a.at(i);                  //返回第i个元素
    a.size();                 //返回a中元素的个数
    a.capacity();             //返回a总共可容纳的元素个数
    a.empty();                //返回值为bool类型,检查容器a是否为空
    a.clear();                //清空a中的所有元素
    a.push_back(1);           //向a的尾部推入元素1
    a.pop_back();             //删除容器a的最后一个元素,没有返回值
    a.back();                 //返回容器a的最后一个元素
    a.front();                //返回容器a的第一个元素 
    a.begin();                //返回一个指向容器a的首元素的迭代器
    a.end();                  //返回一个指向容器a的尾元素下一个位置的迭代器

  注意:

  1. a.at(i)和a[i]的不同之处在于,a.at(i)会做安全检查,如果访问越界,则会抛出out_of_range异常,而a[i]不会做安全检查,直接中断程序。
  2. 相信很多人和我有同样的疑问,为什么不在pop_back()操作时直接返回删除的值,这样不是更加方便?其实pop_back()是没有返回值的,back()才返回容器尾部元素,这样做是为了出于安全考虑,把元素的得到方法和删除方法分开实现,免得在一起操作是,元素删除成功,而返回失败,造成数据丢失。这个规则在其他容器中也同样适用。

  vector的一些不常用操作(慎用):

    a.erase(a.begin());                                //删除传入的迭代器指向的元素,返回值是一个迭代器,指向被删除元素的下一个元素的迭代器
    a.erase(a.begin(), a.begin() + 3);                 //删除迭代器指向区间内的元素(左闭右开),返回指向“右开”元素的迭代器
    a.insert(a.begin(), 5);                            //在传入迭代器的前部插入元素5
    a.insert(a.begin(), 3, 5);                         //在传入迭代器的前部插入3个元素5    
    a.insert(a.begin(), b.begin(), b.end());           //在传入迭代器的全部插入区间的全部元素

   注意:第一点是erase()函数返回值是一个迭代器,指向被删除元素的下一个元素,第二点是在使用迭代器对容器进行遍历时,对特定元素进行删除时,要注意野指针的问题,解决办法是erase(iter--)。PS:这个坑刚开始学习使用的时候很容器踩。

  vector的几种重要算法(包含在头文件<algorithm>中):

    sort(a.begin(), a.end());                     //将传入的迭代器指向区间进行从小到大排序
    reverse(a.begin(), a.end());                  //将传入的迭代器指向区间进行元素颠倒操作
    copy(a.begin(), a.end(), b.begin());          //将传入的迭代器指向区间的元素全部复制到b中,覆盖掉元素元素
    find(a.begin(), a.end(), 10);                 //在传入的迭代器指向区间查找指定元素,返回首次发现该元素的迭代器(返回值是迭代器,不是位置)
  •  List

  list容器的功能实际上与数据结构中的双向链表类似,它的内存空间是不连续的,通过指针来对数据进行访问,它在链表的任意位置的增删节点的操作都是高效的,没有提供[]来对元素进行访问,因为这种访问操作效率很低,list的迭代器只能进行“++”和“--”操作,而不能和vector的迭代器一样进行+n和-n操作。

  与vector不同的一些操作:

    list.push_front(1);        //将传入的元素插入到list的首部
    list.pop_front();          //删除链表的首元素

  相比vector,增加了对list的首元素进行增加和删除的操作。

  • deque

  deque容器算是对list和vector的一种综合,首先在内存上来说,vector采用的是连续的内存空间存储数据,list是采用单个节点的链表形式,而deque是采用的部分连续的非连续空间进行存储,相较于vector的单端(尾部操作),deque和list一样,是双端操作,而且具备了list所不具备的at()和[]操作来访问元素,但是不像vector那样高效,综合了两种的优缺点,是两者折中的一种数据结构。

二、关联式容器

  与顺序性容器不一样,关联式容器是采用非线性的树结构来存储数据,树形结构的底层采用的是平衡二叉搜索树:RB-Tree(红黑树),哈希结构底层实现为哈希表,容器内的各元素没有严格意义上的物理内存上的顺序关系。此外,关联式容器是采用key-value形式,在树形结构中保存key值,然后通过哈希表访问其value值,而顺序性容器只能保存一种类型的值。

  • set和multiset

  set容器顾名思义是一个集合,用来存储统一类型的数据,此外在set中的每个元素的值都是唯一的,而且系统能根据元素的值自动进行排序,默认升序排列,set中的值不能直接改变,很简单理解,对一颗红黑树的任意一个节点的值直接改变后,会直接打破红黑树的本身规则,所以一般都是先删除该节点,然后再添加新节点的形式来完成改值的操作。

  set的常用操作:

    s.erase(1);                        //在set中删除传入的元素
    s.erase(s.begin());                //删除传入的迭代器所指向的元素
    s.erase(s.begin(), s.end());       //删除传入两个迭代器之间的所有元素
    s.insert(2);                       //向容器中插入传入的元素
    s.find(3);                         //在容器中查找传入的元素,如果找到,则返回位置,没有找到则返回end()
    s.equal_range(5);                  //查找找到的第一个大于或等于指定元素的位置,
    s.lower_bound(5);                  //返回第一个大于等于指定元素的定位器
    s.upper_bound(5);                  //返回最后一个大于等于指定元素的定位器
                                       //最后这是三个用法我也没使用过

    注意:

  1. set容器的find()操作的时间复杂度是O(logN),显然其查找效率高于顺序性容器的O(N),此外set容器的erase()和insert()操作的时间复杂度都是O(logN)。(这里挖个坑,有时间写红黑树等BBST的总结)
  2. set容器的动态操作(增删)不会改变之前保存的iterator失效,这点和list容器类似,因为内存上没有连续性

  multiset容器是多重集合,其实现方式和set相似,只是multiset中的元素允许重复出现。

  • map和multimap

  map容器存放的是key-value形式的数据,key和value的数据类型可任意选择,单纯考虑key值,其结构形式和set类似,不同的地方在于每一个key值通过哈希表映射到一个value值,key值是唯一的,不能重复,而value是可以重复的,它的查找操作和删除操作时间复杂度也是O(logN),key值不允许修改,通过key值改变value值很简单,只需要改变映射关系就可以了。

  map的构造函数:

    map<int, string> map_a;                                   //默认构造
    map<int, string> map_a(map_b);                            //拷贝构造
    map<int, string> map_a(map_b.begin(), map_b.end());       //区间构造
    map<int, string> map_a{ {1,"a"},{2,"b"} };                //列表构造

  map的插入操作:

    map_a.insert(pair<int, string>(3, "c"));
    map_a.insert(map<int, string>::value_type(4, "d"));
    map_a[5] = "e";

  注意:以上三种方法都可以完成数据的插入操作,但是还是有差异,前面两种在效果上是一直的,在使用insert()插入数据时,如果map容器中已经含有key值,则会直接跳过,不进行任何操作,不会用新的value值覆盖原始的映射关系,而采用第三种数组的方式,如果map容器中已经含有key值,会进行value值的覆盖操作。但是单就效率而言,如果确定map容器中不存在该key值,用insert()操作更高效。

  map容器的其他操作都与set容器的操作相同。而multimap容器就是允许集合中出现重复元素,与multiset类似。

三、容器适配器

  刚开始学的时候不理解这个概念,感觉很高端的样子。其实就是将已有的容器结构类型,改变一下接口,使其实现特有的功能,比如stack容器,其实就是deque容器改变接口实现的,你也可以使用deque容器当做stack容器使用,但是deque容器接口过多,为了避免误操作,破坏了stack容器的性质,就使用容器适配器。C++提供了三种容器适配器:stack,queue和priority_queue。stack和queue默认是基于deque实现,priority_queue默认是基于vector实现。

  • stack  

  stack容器专门设计用于在LIFO(后进先出),其中元素仅从容器的一端插入和删除。stack容器的成员函数:

    stack<int> stack;
    stack.empty();       //判断栈是否为空,为空返回true,否则返回false
    stack.size();        //返回栈中元素的个数
    stack.push(1);       //将传入元素压入栈中
    stack.top();         //返回栈顶元素的引用
    stack.pop();         //将栈顶元素弹出,无返回值

  stack容器看着很简单,成员函数也很少,其实用处很大,比如在递归中,使用栈就能完成一次深度优先搜索,而且节约了递归中的函数入栈出栈开销。还有波兰表达式、逆序输出等等。

  • queue

  queue容器是一种“先进先出”的队列,queue的成员函数:

    queue<int> queue;
    queue.empty();        //判断栈是否为空,为空返回true,否则返回false
    queue.size();         //返回队列中元素的个数
    queue.push(1);        //将元素入队
    queue.pop();          //弹出队首元素,无返回值
    queue.front();        //返回队首元素的引用
    queue.back();         //返回队尾元素的引用

  queue容器的应用也很广泛,比如对二叉树的层次遍历就要使用到queue容器,理解的更深刻一点的话,广度优先搜索就会使用到队列的结构。

  • priority_queue

  priority_queue容器就是优先级队列,按照值的大小来决定出队顺序。

    priority_queue<int> p1;                                 //默认是 最大值优先级队列 
    priority_queue<int, vector<int>, less<int> > p2;        //提前定义好的预定以函数,第二个参数的默认是less<int>
    priority_queue<int, vector<int>, greater<int>> p2;      //最小值优先级队列

  

原文地址:https://www.cnblogs.com/maybe-fl/p/12814430.html