1.顺序容器
list列表 (基于双向链表,非连续)
某些STL中用的甚至是双向循环链表。既然是基于双向链表显然其不是连续存储的。
所以每个节点不光包括val 值,还有前后两个指针。
list包含了一个指针,重载了运算符,实现迭代器的递增递减与取值操作
list实际上是一个带有头节点的双向循环链表,node指针指向空的头节点
由此begin就是node->next的val
end 就是node本身,因为是循环的
empty判断node->next==node?即可
back就重载–,找到node的上一个节点。
注接下文array:
不确定大小/频繁插入删除用list
大量查找,用array
这样符合我们操作系统里的局部性原理。
array 固定数组,连续存储
用法:
array<种类,数目> name {具体数值初始化};
因为array容器中新增了类似at()这样的成员函数,其会检查n是否有效,否则抛出异常,所以比普通数组安全。
vector 动态数组(连续空间,基于三个指针)
动态数组,用于 O(1) 的随机读取.
实现原理:
由三个指针实现,
第一个指针指向vector起始位置
第二个指针指向目前有数据的末尾
第三个指针指向整个vector容器所占内存的末尾。
由此实现:
template<class _Ty,
class _Ax>
class vector
: public _Vector_val<_Ty, _Ax>
{ // varying size array of values
public:
/********/
protected:
pointer _Myfirst; // pointer to beginning of array
pointer _Mylast; // pointer to current end of sequence
pointer _Myend; // pointer to end of array
};
由此:
注意:
end 指向第二个指针
size 是2-1 而capacity 是3-1
注意resize的时候,是新在堆中开辟内存,赋值之后删除旧数组
template <class _Ty, class _Alloc = allocator<_Ty>>
class vector{
public:
iterator begin() {return _Myfirst;}
iterator end() {return _Mylast;}
size_type size() const {return size_type(end() - begin());}
size_type capacity() const {return size_type(_Myend - begin());}
bool empty() const {return begin() == end();}
reference operator[] (size_type n) {return *(begin() + n);}
reference front() { return *begin();}
reference back() {return *(end()-1);}
...
};
详细使用见:
添加链接描述
deque 双端队列 数组挂载小段空间,连接非连续的空间
支持O(1)的随机读取和头部、尾部的频繁增删
为了管理这些连续空间,deque 容器用数组(数组名假设为 map)存储着各个连续空间的首地址。也就是说,map 数组中存储的都是指针,指向那些真正用来存储数据的各个连续空间。
当 deque 容器需要在头部或尾部增加存储空间时,它会申请一段新的连续空间,同时在 map 数组的开头或结尾添加指向该空间的指针,由此该空间就串接到了 deque 容器的头部或尾部。
遍历的时候四个指针,一个头first一个尾last,一个cur,最后一个node指向map数组指针,如果cur遍历到边缘,就用node指针来更新前三个指针。
由这四个指针,维护两个迭代器:一个start记录map数组中首个连续空间的信息,一个finish迭代器记录map数组最后一个连续空间的信息。但是start的cur指向连续空间的第一个数,而finish的cur指向连续空间的最后一个数。
2.基于上述容器产生的数据结构
stack 栈(基于deque)
默认基于deque实现,后入先出LIFO。
常用于深度优先搜索,字符串匹配等问题。
堆和栈的区别
首先要了解内存分区:
- 栈区:由编译器分配释放,存储局部遍历和函数参数等。
- 堆区:由程序员分配释放,由malloc和free负责
- 自由存储区:可以是堆也可以是静态存储区(取决于operater new的细节实现方式);所以new和delete从自由存储区分配。
- 全局/静态区:全局遍历和静态变量。
- 常量存储区:存放常量,不允许修改
区别:
- 管理方式:堆由程序员控制,容易内存泄漏。而栈由编译器控制。
- 申请后相应方式:操作系统有一个记录空闲内存的链表,申请堆时遍历链表,寻找第一个大于需要空间的堆节点,删除该节点分配给程序。而栈只要剩下的空间够申请就一直提供内存,反之则栈溢出。
- 申请大小限制:堆是不连续的,理论上看虚拟内存多大就能多大。而栈是连续的,一般是一两M大小。
- 效率:栈在计算机底层有专门寄存器提供服务,很快。相反堆需要库函数,比较慢。
- 碎片:堆频繁new和delete会造成大量碎片,降低程序效率。
- 生长方向:堆向高地址方向增长,栈向低地址方向。
queue 队列(基于deque)
默认基于deque,先入先出FIFO
常用于广度优先搜索。
priority_queue(基于vector)
最大值先出的数据结构,默认基于vector实现堆结构。它可以在O(nlogn)的时间排序数组,O(logn) 的时间插入任意值,O(1) 的时间获得最大值,O(logn) 的时间删除最大值。priority_queue 常用于维护数据结构并快速获取最大或最小值。
3.有序数据结构
set 有序集
默认基于红黑树(一种特殊的二叉查找树)。元素不可重复。
它可以在 O(nlogn) 的时间排序数组,O(logn) 的时间插入、删除、查找任意值,O(logn) 的时间获得最小或最大值。这里注意,set 和 priority_queue 都可以用于维护数据结构并快速获取最大最小值,但是它们的时间复杂度和功能略有区别,如priority_queue默认不支持删除任意值,而set获得最大或最小值的时间复杂度略高,具体使用哪个根据需求而定。
map 有序表
在set的基础上加上映射关系,使得每个key存一个value值。
4.无序数据结构(对每个有序容器作哈希处理)
unordered_set 哈希集
可以在 O(1) 的时间快速插入、查找、删除元素,常用于快速的查询一个元素是否在这个容器内。
如果需要每次判断一个元素在不在一个vector内,我们可以将其转化成一个哈希集:这样就能很快的查找:
unordered_set<int> dict(v.begin(),v.end());
if(v.count(a))
{
...
}
unordered_map 哈希表
在 unordered_set 的基础上加上映射关系,可以对每一个元素 key 存一个值 value。在某些情况下,如果 key 的范围已知且较小,我们也可以用 vector 代替 unordered_map,用位置表示 key,用每个位置的值表示 value
详情见:
添加链接描述