• 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;      //最小值优先级队列

      

  • 相关阅读:
    负载均衡——LVS DR模式
    Java之BigDecimal详解
    Spring AOP代理对象创建流程
    spring aop切面不生效
    aspectj-autoproxy Controller未生效解决方案
    jvm参数分类
    dubbo优雅停机
    Dubbo 协议注意项
    dubbo provider
    查找java_home的安装路径
  • 原文地址:https://www.cnblogs.com/maybe-fl/p/12814430.html
Copyright © 2020-2023  润新知