我之前的博文中有专门的5篇整理并介绍了STL的概念:
STL1——整体介绍:https://www.cnblogs.com/grooovvve/p/10467794.html
STL2——泛型编程(模板类、迭代器):https://www.cnblogs.com/grooovvve/p/10467797.html
STL3——函数对象:https://www.cnblogs.com/grooovvve/p/10467800.html
STL4——算法:https://www.cnblogs.com/grooovvve/p/10491685.html
STL5——其他库总结:https://www.cnblogs.com/grooovvve/p/10491705.html
通过复习和提炼其实有助于加深对这些概念的理解。
STL 的英文全称叫做Standard Template Library,就是标准模板库的意思。
STL编程是一种泛型编程的思想。泛型的体现就是使用模板类来编程,模板类使得类可以独立于具体的数据类型;
这样子编程更加灵活,增强代码的复用;
STL中提供了一组容器、迭代器、函数对象、和算法的模板;STL是提供了一组模板;
====================================================
一、什么是容器:
容器是用来存储数据的,数据的类型可以是用户自定义的,也可以是预定义的。
这里主要要关注七大类容器:vector、list、deque、map、set、stack、queue;
这七大类容器按类型可分为三种:
顺序性容器:vector、list、deque;
关联性容器:set、map(及其衍生的multiset、multimap);
容器适配器:stack、queue;
接下来分别介绍一下这七大容器,然后再对三种容器类型进行总结;
1、vector:中文名叫做向量,其实就是一种封装了动态大小的数组的顺序性容器、也可以理解成一个序列。可以把它简单理解成存放任意数据类型的动态数组。和静态数组一样具有随机访问的能力。也就是说访问任意索引的元素的时间复杂度都是O(1)。vector支持对序列中的任意元素进行快速直接访问。
对于vector的操作有很多,最常见的几种先熟悉一下:
push_back() 往序列尾加入元素;
empty() 判断向量是否为空;
size() 判断向量中元素的个数;
当然顺序性容器的特点就是可以用for循环进行遍历;
2、list:其底层实现是一种双向链表结构,所以相比于vector而言,其允许快速的插入和删除,但是随机访问比较慢。 vector的随机插入和删除比list慢,但是随机访问快。所以二者也算是各有优劣,根据实际情况选择性使用哪种。
3、deque:更像是vector的升级版,是一个双端的动态数组,无论在尾部还是在头部插入元素都比较迅速。但是在中间插入元素比较慢。
所以总结来看vector、deque的本质就是数组、list的本质就是链表。
之前的vector、deque是方便访问,不方便修改元素;list是方便修改元素,不方便访问。
那么对于一些既要修改又要访问的场景,该怎么办?这就涉及set(集合) 和map(映射、字典);
首先我们要知道set、map都是高级的数据结构,其底层实现是可以不一样的,可以是某种搜索树结构、也可以用哈希表实现。
哈希表的实现可以使得访问、修改元素的时间复杂度都为O(1)。但是会失去了数据的顺序性。
树结构的访问和修改都是O(logn)级别的,也就是说是折中了访问和修改的性能。而且数据具有顺序性。
set、map主要用于解决一些查找问题。查找有无、查找对应关系(例如:元素-频率)。
4、set :是一种关联性容器,存储了一堆同类型的元素,这些元素必须都是唯一的。
A standard container made up of unique keys, which can be retrieved in logarithmic time.
常见的操作有:
find() 查找元素是否存在;
insert() 插入元素
empty() 集合是否为空;
...
5、map:map中的元素是成对存在的,我们管它叫做键值对,C++中有pair、make_pair去声明和构建键值对。折中键值对适合用于查找对应关系,例如某个元素出现的频率等。
常见的操作有:
find() 查找元素是否存在;
insert() 插入元素;
empty() 集合是否为空;
...
有一种map叫做unordered_map,其底层实现是哈希表,因此其查找速度非常快,缺点是哈希表的建立比较费时而且占用内存。对于查找问题使用unordered_map会跟快。同理set也有对应的unordered_set;这种set、map我们管它叫无序集合、无序映射。
stack栈、queue队列、priority_queue优先队列也是高级的数据结构,其底层实现可以是数组、也可以是链表。
6、stack
栈的特点是只能对栈顶元素进行操作。是一种先进后出的结构FIFO,
栈的操作:主要有压栈push、出栈pop、查看栈顶元素top、是否为空empty;
栈的结构和操作虽然简单,但是使用起来非常灵活。
而且栈和递归的关系非常密切。递归的操作就是通过栈的结构实现的。
栈顶元素反应了嵌套的层次关系中,最近要匹配的元素。
二叉树的的前中后序递归遍历、非递归遍历实际上就是应用了栈的原理;
7、queue
是一种先进先出的结构FILO。队尾进元素,队首出元素。
优先队列比较特殊的是出队的逻辑发生了改变,最高优先级的元素先出队。这个最搞优先级怎么定义视情况而定。实际上优先队列的底层实现是堆的结构(最大堆、最小堆)。堆的底层实现实际上是树的结构。链表实际上是特殊的树。这里不展开讲了。
队列的操作有:入队push、出队pop、获取对顶元素top、队列是否为空empty;
====================================================
二、关于迭代器:
在数组这样的结构中,遍历所有的元素显得很自然很方便。就是用数组下标访问即可。
但是要遍历STL的容器中元素该怎么做呢?这就要使用迭代器。迭代器本质是一种对象。
每种容器,都有其对应的迭代器对象。声明了相应容器的迭代器对象之后,就可以用迭代器访问该容器的任一元素。
迭代器最重要的意义在于,其使用起来都是一样的且十分方便,屏蔽了不同容器的差异。
例如下方代码,使用迭代器iter遍历prority_queue中所有元素。容器提供了两种方法begin()、end()表示容器的第一个和最后一个元素。
迭代器的累加重载了++运算符,使得使用起来相当方便。
1 priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>> pq; 2 for(unordered_map<int,int>::iterator iter = freq.begin(); iter!=freq.end(); iter++) 3 { 4 if(pq.size()==k) 5 { 6 if(iter->second > pq.top().first) 7 pq.pop(); 8 pq.push(make_pair(iter->second,iter->first)); 9 } 10 else 11 pq.push(make_pair(iter->second,iter->first)); 12 }
====================================================