总结-STL

STL, by Flowyuan




1 分类

\(顺序容器\begin{cases} vector&\text{数组}\\ deque&\text{双端队列}\\ list&\text{双端链表}\\ string&\text{字符串,与vector类似,专门用于保存字符}\\ \end{cases}\)
这里讨论的顺序容器不包含array和forward_list

\(关联容器\begin{cases} 有序 \begin{cases} set\\ map\\ multimap\\ multiset\\ \end{cases}\\\\ 无序 \begin{cases} unordered\_set\\ unordered\_map\\ unordered\_multimap\\ unordered\_multiset\\ \end{cases} \end{cases}\)

\(容器适配器\begin{cases} stack&\text{栈}\\ queue&\text{队列}\\ priority\_queue&\text{优先队列}\\ \end{cases}\)

\(其它容器\begin{cases} pair\\ turple\\ bitset \end{cases}\)

\(泛型算法\)


2 容器操作

若无说明,以下操作对所有容器、容器适配器都适用

2.1 构造函数

C c;\\空容器
C c1(c2);\\构造c2的拷贝c1,容器类型和元素类型要求相同
C c(b,e);\\构造c,将迭代器b,e之间的元素拷贝到c,不过要求储存元素类型相同,对容器类型没有限制
C c{a,b,c};\\列表初始化

顺序容器专用

C seq(n);\\不提供初值,初始化一个大小为n,所有元素为默认初值的顺序容器,自定义元素类型要有默认构造函数
C seq(n,t);\\提供初值

2.2 赋值和swap

赋值

c1=c2;//相同类型
c1={a,b,c};

赋值后左边的容元素个数小和内容均与右边一致。
swap

swap(c1,c2)\\交换c1和c2中元素,时间复杂度O(1),因为元素本身并未交换。

以下只适用顺序容器

seq.assign(b,e)//b,e表示一个区间的左右指针,同初始化
seq.assign(n,t)//替换为n个值为t的元素,第二个参数绝对不能缺

2.3 容器大小与关系运算符

c.empty();
c.size();

关系运算符
除无序关联容器外所有的容器都支持光系运算符,关系运算符左右两边必须容器累心和储存元素类型相同。

  • 当且仅当两容器大小相同且对应位置元素相等两容器才相等
  • 大小不等,小的容器是大的前缀子序列,那么大的容器更大
  • 第一个对应不相等的位置哪个容器的元素大哪个哪个容器大

容器等于的比较通过==运算符实现,其它关系的比较都有<实现。所以对于容器内为自定义元素类型,如果有比较的场合,只需重载<==即可。(其实acm一般重载小于就可以了)


3 顺序容器操作

以下内容只涉及顺序容器的操作,若无说明则全都适用。

3.1 添加元素

string和vector不支持push_front()

c.push_back(t);//尾部插入一元素,返回void
c.push_front(t);//头部插入一元素,返回void
c.insert(p,t);//迭代器p前插入元素t,返回插入的新元素位置的迭代器
c.insert(p,n,t);//迭代器p前插入n个t,返回第一个新元素位置的迭代器,n为0返回p
c.insert(p,b,e);//迭代器前插入[b,e)范围内元素,范围为空,返回P
c.insert(p,il);//迭代器p前插入列表il,il是指一个花括号包围的元素列表

3.2 访问元素

at()和[]不适用list

c.back();//返回尾元素的引用,若c为空一般会报错
c.front();//返回首元素的引用,c为空一般会报错
c.at(n);//同c[n],不过越界会抛出异常
c[n];

3.3 删除元素

vector和string不适用pop_front()

c.pop_back();//删除尾元素
c.pop_front();//删除首元素
c.erase(p);//删除迭代器p位置上的元素,返回被删元素后的元素
c.erase(b,e);//删除区间[b,e),返回指向删除区间后一个元素的迭代器
c.clear();//删除c中所有元素,返回void

3.4 改变容器大小

c.resize(n);//大小调为n,若原大小小于n,则尾部插入0至n。如果等于不做改变,如果大小大于n,则删除至n
c.reszie(n,t);//同上,改变在于插入的元素为t而不是0

扩容的说明

  • vector,string,deque都是倍增扩容,一般来说,每次扩容都是原来申请两倍的空间,把旧元素先拷贝入新空间,再对加入的新元素进行操作(deque不是很清楚,但至少string和vector是这样)。
  • 对于不断push_back()n个元素到上述三类容器末尾,可以保证扩容需要的额外时间代价不会超过n的整数倍。
  • resize()等其它以上提到的函数只会对元素个数进行操作,实际分配的内存只增不减。

3.5 string的额外方法

STL好麻烦==。
会省略掉很多。

增删改查

string s1(s2,pos2,len2);//len2可省略
s1.insert(pos1,str2,pos2,len2);//pos2,len2可省略,在pos1位置前插入,末尾插入则pos1替换成s1.size().
s1.insert(pos1,n,c);//pos1前插入n个字符c
s1.erase(pos1,len1);
s1.replace(pos1,len1,s2);//替换[pos1,pos1+len1)为s2
s1.replace(pos1,len1,n,c);
s1.find(c/s2,pos1);从位置pos1开始查找字符c/字符串s2,如果存在返回第一次出现位置(s2则是s2首字符的位置),不存在返回unsigned int的-1。pos1不填默认为0。
s1.rfind(c/s2);同上。区别在于如果c/s2存在,返回最后一次出现的位置。
s1.substr(pos1,len1);//返回以pos1开始长度为len1的子串,len1可省略

数值转换

很好记,希望常数不要太大

s=to_string(i);//把数字i转化成字符串s,适用于所有数字类型
//从字符串到数字
i=stoi(s);          //string->int
i=stol(s);          //string->long
i=stoll(s);         //string->long long
i=stoul(s);         //sting->unsigned long
i=stoull(s);        //string->unsigned long long.
i=stof(s);          //string->float
i=stod(s);          //string->double
i=stold(s);         //string->long double.

4 容器适配器

4.1 说明

本质上适配器是一种机制,能使一种容器看上去像另外一种容器。一般来说适配的都是顺序容器。
stack可以适配任何一种容器,queue不能适配vector(因为vector不能pop_front()和push_front()),priority_queue需要随机访问,不能用list。(当然不考虑string了)
stack和queue默认deque
priority_queue默认vector

4.2 操作

A a;
stack<string,vector<string> > stk;//例子
A a(b);//创建一个名为a的适配器,带有b容器的拷贝。只要求容器类型和元
素类型一致,b是不是适配器没有关系。
关系运算符//和其它容器同样的比较规则
a.empty();
a.size();
swap(a1,a2);//适配器类型,容器类型,元素类型一致。O(1)
s.pop();s.top();s.push();//stack
q.pop();q.front();q.back();q.push();//queue,queue是可以返回队尾元素的
q.pop();q.top();q.push();//priority_queue

优先队列自定义排序,只提供一种方法

struct node{
    int a,b; 
};

struct cmp{
    bool operator()(node x,node y){
        if(x.a!=y.a) return x.a>y.a;//x.a小的优先;如果为小于号则大的优先
        else return x.b>y.b;//若x.a与y.a相等,那么x.b小的优先;如果是小于号则大的优先
    }
};

priority_queue<node,vector<node>,cmp> que;//x.a

例如:

struct node{
    int x,y;
    bool operator<
}

5 关联容器

5.1 概要

8种容器主要区别分为以下三类

  • set和map。set保存的是关键字,而map保存的是关键字和关键字对应的值。
  • 如果有multi则关键字允许重复,否则不会重复。
  • 如果带有unordered则无序,用哈希函数按键值来组织元素。否则有序,用红黑树按键值来组织元素。

虽然set和map看上去差别比较大,实际上map不过就是比set增加了关联元素值的操作而已,它们的所有操作都是按着键值来的,两者差别并不大。
map的定义为map<T1,T2>,实质上每个元素类型是pair<const T1,T2>.

5.2 定制

unordered不能定制,因为它是用哈希函数的。
和优先队列类似,不过优先队列顺序是反着的。

set<int,geater<int>> sett;//按键值从大到小排序
set<int,less<int>> sett;//按键值从小到达排序,不填第二个参数默认less<int>
map<int,geater<int>> mapp;//按键值从大到小排序
map<int,less<int>> mapp;//按键值从小到达排序,不填第二个参数默认less<int>
//自定义:
struct node{
    int x,y;
}
struct cmpkey{
    bool operator()(node a,node b){
        if(a.x!=b.x) return a.x>b.x;//键值的x元素大的在前面
        return a.y<b.y;//键值的x相等,那么的y小的在前面
    }
}
map<node,int,cmpkey> mapp;//int不参与排序,排序依照node和定义的cmpkey
set<node,cmpkey>;//排序依照node和定义的cmpkey

5.3 插入

c.insert(v);//插入,对于非multi只有当关键字不存在的时候才能成功插入,multi则每次都成功插入,下面同理。v是元素的类型,map的话是pair<T1,T2>。
c.insert(b,e);//插入范围[b,e)
c.insert(il);//列表插入
c.insert(p,v);//将迭代器p作为一个提示,指出从哪里开始搜索新元素应该存储的位置。

对于map,由于元素类型是pair<T1,T2>,所以c.insert(v)有三种方式

mapp<T1,T2> c;
c.insert(pair<T1,T2>(key,val));//临时对象插入
c.insert(make_pair<key,val>);//make_pair函数
c.insert({key,val});//指定值

map和unordered_map也可以通过下标插入

5.4 删除

c.earse(k);//删除关键字为k的元素。multi的话全部删除,返回删除的匀速数目
c.earse(p);//删除迭代器p指向的元素
c.earse(b,e);//删除范围[b,e)

5.5 下标操作

set下标操作没多大意义,所以没有下标操作,由于multi的一个键值可能对应多个值,所以multi也没有下标操作。
所以只能对map和unordered_map

c[key]=val;//不存在,那么插入(key,val);存在,修改c[key]为val

5.6 访问元素

c.find(k);//查找第一个关键字为k的元素,不存在返回尾后迭代器
c.count(k);//返回关键字等于k的元素数量
c.lower_bound(k);//返回一个迭代器,指向第一个关键字大于等于k的元素。如果关联容器用greater<T>定义,则返回第一个关键字小于等于k的元素。如果不存在,则返回尾后迭代器
c.upper_bound(k);//返回一个迭代器,指向第一个关键字大于k的元素。如果关联容器用greater<T>定义,则返回第一个关键字小于k的元素。如果不存在,则返回尾后迭代器
c.equal_range(k);//返回一个迭代器pair,表示关键字等于k的元素区间。区间左闭右开(无论什么排序规则)。不存在,则两个成员都等于c.end()

5.7 unordered的额外说明

时间:

  • 非unordered:基于红黑树,复杂度与树高相同,即O(logn)。稳定
  • unordered:基于散列表,复杂度依赖于散列函数产生的冲突多少,但大多数情况下其复杂度接近于O(1)。最坏O(n)。不稳定

空间:
unordered的散列空间会存在部分未被使用的位置,所以其内存效率不是100%的。而红黑树的内存效率接近于100%。但是红黑树依然需要很多的额外空间。所以两者的空间开销都比较大。

摘自 https://www.cnblogs.com/yimeixiaobai1314/p/14375195.html

摘自C++ prime上的补充:
无序容器在存储上组织为一组桶,每个桶内保存0个或多个元素。对于相同的参数,哈希函数必须总是产生相同的结果。理想情况下,哈希函数还能将每个特定的值映射到唯一的桶。但是将不同的关键字映射到同一个桶也是允许的。当一个同保存多个元素时我们要顺序搜索来查找我们想要的那个。
计算一个元素的哈希值和在桶中搜索通常时很快的操作。但是如果一个桶中保存了很多元素,那么时间复杂度会比较大。

6 其它容器

6.1 pair

pair<T1,T2> p;
pair<T1,T2> p(v1,v2);//指定值
pair<T1,T2> p{v1,v2};//指定值
make_pair(v1,v2);//临时对象,类型根据值来推定
pair<T1,T2>(v1,v2);//临时对象,类型为分别为T1,T2
p.first;//返回第一个
p.second;//返回第二个
比较按照字典序

6.2 tuple

tuple的个数任意,成员类型也可各不相同。不过每个确定的tuple类型的成员数目是固定的。

tuple<T1,T2,...,Tn> t;
tuple<T1,T2,...,Tn> t(v1,v2,...,vn);
make_tuple(v1,v2,...,vn);
get<i>(t);//返回t的第i个元素的引用

两tuple能比较的条件

  • 元素数目相等
  • 对应位置的元素是可以比较的(即小于和等于是合法的)

6.3 bitset

概述
bitset,类似于bool数组,不过优化了空间。一个bool占8bit,所以相同大小下占用空间bitset是bool的1/8。所以如果整数范围过大可以考虑bitset来标记(比如欧拉筛),或者状压的时候如果ll位数不够,就可以用bitset。
对于时间方面,bitset的整体操作比bool数组优秀,不过对位修改会比bool数组慢。小心卡常。
或许会想到bitset高精,不过这个大概会比正常写慢个几十倍。这位dalao分析得很棒。
https://zhuanlan.zhihu.com/p/72730434
构造

bitset<n> b;//b有n位,每一位为0;
bitset<n> b(u);//b是ull类型的值u的低n位的拷贝。如果n大于ull大长度,则高位赋值位0。
bitset<n> b(s,pos,len);//字符串从pos位置开始长度位len的拷贝。只能包含0和1
bitset<n> b(cp,pos,m);//char*

由于bitset是从右向左编号从0~n,所以string的下标最大的对应bitset第0位,以此类推。
操作
可以进行所有位运算操作,~, |, &, ^, >>, <<

b.any();//存在1返回true,否则false
b.all();//全为1返回ture,否则false
b.none();//全为0返回ture,否则false
b.count();//返回1的个数
b.size();//返回位数
b.flip();//按位取反
b[pos];//访问下标位pos的位
b.to_string(zero,one);转string,0转成zero,1转成one;不填默认'0'和'1'
b.to_ul();//转ul
b.to_ull();//转ull
cout<<b;//二进制输出
cin>>b;//读入字符串再转bitset

细节:

  • 转ull或ul:bitset位数过低ull或ul二进制高位自动补0,位数过高只要超出ull或ul的长度的高位上全为0就可以正常计算,否则会抛出异常
  • 位运算可以与int、ll、ull等进行

7 泛型算法

大多再algorithm头文件下,只记录我觉得有用的。一些复杂的如lambda就算了。
一般来说关联容器不用泛型算法。

简单查找

find(b,e,val);
find_if(b,e,cmp);//cmp中填入要查找的元素属性,满足要求返回1
//找到了返回指针/迭代器,找不到返回e
count(b,e,val);
count_if(b,e,cmp);//
//返回满足条件的值的出现次数
search(b1,e1,b2,e2);//再[b1,e1)搜索子序列[b2,e2)。找到返回第一次出现位置,否则返回e1

二分查找

upper_bound(lef,rig,k);//对于一个升序序列
//cmp函数的书写
bool cmp(const int &x,const int &y){return x>y;}
upper_bound(lef,rig,k,cmp);//对于一个降序序列

注意cmp函数里参数类型不可填int &

  • 对于升序,不填cmp函数:
    upper_bound函数返回指向大于k的第一个元素的指针(迭代器),找不到就返回填入的区间右端点,也就是返回指向 | 区间末尾元素的后一个元素 | 的指针(迭代器)。
    lower_bound函数返回指向大于等于k的第一个元素的指针(迭代器),找不到就返回填入的区间右端点,也就是返回指向 | 区间末尾元素的后一个元素 | 的指针(迭代器)。
  • 对于降序,填cmp函数:
    upper_bound函数返回指向小于k的第一个元素的指针(迭代器),找不到就返回填入的区间右端点,也就是返回指向 | 区间末尾元素的后一个元素 | 的指针
    lower_bound函数返回指向小于等于k的第一个元素的指针(迭代器),找不到就返回填入的区间右端点,也就是返回指向 | 区间末尾元素的后一个元素 | 的指针

排序

参数略

sort();//快速排序,不稳定
stable_sort();//归并排序,稳定

去重

unique(b,e);//升降序无所谓,只要求相等元素挨在一起,第三个参数自定义相等规则

最大值和最小值

min(v1,v2);
max(v1,v2);
min(il);//il是大括号括起来的列表
max(il);
min_element(b,e);//最小元素的迭代器
max_element(b,e);//最大元素的迭代器

写入元素

fill(b,e,val);//[b,e)写入元素val,用于初始化

排列

前提假定每个元素都不相等

next_permutation(b,e);//字典序的下一个排列
prev_permutation(b,e);//字典序的上一个派略
//如果序列不是最后一个排列,则返回ture,如果序列已经是最后一个排列,则返回false,同时变为字典序最小/大的排列
//填入第三个cmp函数可以规定字典序变化的方向,不过已经提供了next和prev两个方向了。

翻转

reverse(b,e);//翻转区间[b,e)
posted @ 2021-07-14 16:20  七铭的魔法师  阅读(55)  评论(0编辑  收藏  举报