c++的STL常用库的使用大总结

STL中的vector,queue,priority_queue,deque,set,multiset,map,bitset,常用的算法容器

c++一些运算符的优先级从高到低的顺序如下表示

加减 移位 比较大小 位与 异或 位或

± <<,>> <,>,==,!= & xor(c++^) |

vector

  1. vector 可理解为变长数组,它的内部实现基础倍增思想。

  2. vector 支持随机访问,对于任意的下标 0<= i <n,可以像数组一样用[i]取值。但是它不是链表,不支持在任意位置o(1)插入。为了保证效率,元素的增删一般应该的末尾进行。

struct rec{..};

vector <rec> c; 自定义的结构体类型也可以保存在 vector 中。

size/empty
//size函数返回vector的实际长度(包含元素的个数),empty函数返回一个bool类型,表明vector是否为空。二者的时间复杂度都是o(1);
//所有的STL容器都支持这两个方法,含义也都相同。
clear
//clear 函数把 vector 清空。
    
迭代器
//迭代器就像STL容器中的“指针”,可以用“*”号操作符解除引用。
//一个保存int的vector的迭代器声明方法为:
vector <int> ::iterator it;
/*vector 的迭代器是“随机访问迭代器”,可以把vector的迭代器与一个整数相加减,其行为和指针的移动类似。可以把vector的两个迭代器相减,其结果也和指针相加减类似
得到两个迭代器对应下标之间的距离。
*/

begin/end
/*
	begin函数返回第一个元素的迭代器。例如a是一个非空的vector,则*begin() 和 a[0]的作用相同。
	所有的容器都可以视作为一个“前闭后开”的结构,end函数返回vector的尾部,即第n个元素再往后的“边界”。*a.end() 与 a[n]都是越界访问,其中n = a.size()。
*/
    
 for(int i=0; i<a.size(); i++) 
     	cout<<a[i]<<endl;
 for(vector<int>::iterator it = a.begin(); it != a.end(); it++)
     	cout<<*it<<endl;

front/back
/*
	front函数返回vector的第一个元素,等价于*a.begin() 和 a[0].
	back 函数返回vector的最后一个元素,等价于*--end() 和 a[a.size()-1].
*/

push_back/pop_back
 	a.push_back(x)  // 把元素 x 插入到vector a的尾部  
  	a.pop_back()	// 删除vector a的最后一个元素

queue

// 头文件 queue 主要包括循环队列 queue 和优先队列 priority_queue 两个容器。
/*
声明
	queue<int> q;
	struct res{...}; queue <rec> q;
	priority_queue <int> q;
	priority_queue < pair<int,int> > q;
*/

循环队列queue

方法 描述 实例 时间复杂度

push 入队(从队尾) q.push(element); o(1)

pop 出队 (从对头) q.pop(); o(1)

方法 描述 实例

front 队头元素 int x = q.front()

back 队尾元素 int y = q.back()

优先队列priority_queue

priority_queue可以理解为一个大根二叉堆。

方法 描述 实例 时间复杂度

push 把元素插入堆 q.push(x); o(log n)

pop 删除堆顶元素 q.pop(); o(log n)

top 查询堆顶元素(最大值)int x = q.top() o(1)

重载 “<” 运算符

​ priority_queue中存储的元素类型 必须定义 “小于号” ,
较大的元素会放在堆顶。内置的int,string等类型本身都可以比较大小。 若使用自定义的结构体类型,则需要重载 “ < ” 运算符。

/*
	例如下面的poi结构体保存了二维平面上点的编号和坐标,比较大小时,先比横坐标,再比纵坐标,并且考虑了精度的误差。
*/
struct poi{int id; double x,y;};
const int eps = 1e-8;

bool operator <(const poi &a,const poi &b)
{
    return a.x+eps<b.x || a.x<b.x + eps && a.y < b.y;
}

priority_queue 实现小根堆

​priority_queue 实现小根二叉堆的方式一般有两种。

对于int等内置函数数值类型,可以把要插入的元素的相反数放入堆中。等从堆取出元素时,再把它取相反数变回原来的元素。这样就相当于把小的放在堆顶。

​更为通用的解决方案是,建立自定义结构类型,重载 “ 小于号 ”,但是当做 “ 大于号来写 ” 来编写函数。

struct res{int id; double value;};
bool operator <(const rec&a, const rec&b)
{
    return a.value > b.value;
}

​ 这样priority_queue会认为 “ 大 ” 的更 “ 小 ”,“ 小 ” 的更 “ 大 ”,从而实现大根堆时,value较小的元素就会放在堆顶。

懒惰删除法

懒惰删除法(又称延迟删除法)就是一种对应的策略。当遇到删除操作时,仅在优先队列之外做一些特殊的记录(例如记录元素的最新值),用于辨别那些堆中尚未清除的
“ 过时 ”元素。当从堆顶取出最值时,再检查 “ 最新元素 ” 是不是 “ 过时 ”的,若是,则重新取出下一个最值。换言之,元素的 “ 删除
” 被延迟到堆顶进行。

deque

双端队列 deque 是一个支持在两端高效插入或删除元素的连续线性存储空间。它就像是 vector 和 queue
的结合。与vector相比,deque 在头部增删元素仅需要 o(1) 的时间; 与 queue 相比,deque像数组一样支持随机访问。

方法 描述 示例 时间复杂度

【】 随机访问 与vector类似 o(1)

begin/end deque的头/尾迭代器 与vector迭代器类似 o(1)

front/back 队头/队尾元素 与queue类似 o(1)

push_back 从队尾入队 q.push_back(x) o(1)

push_front 从队头入队 q.push_front(y) o(1)

pop_front 从队头出队 q.pop_front() o(1)

pop_back 从队尾出队 q.pop_back() o(1)

clear 清空队列 q.clear() o(n)

set

头文件 set 主要包括set 和 multiset 两个容器,分别是 “ 有序集合 ”和 “ 有序多重集 ”,即前者的元素的元素不能重复,而后者可以包含若干个相等的元素。 set 和multset 的内部实现是一颗红黑树 (平衡树的一种),他们支持的函数基本相同。

声明
    set<int> s;
	struct rec{...};  set <rec> s;
	multiset <double> s;
	与优先队列一样,set和 multiset存储的元素必须定义 “ 小于号 ” 运算符。

size/empty/clear

​ 与vector类似,分别为元素的个数,是否为空,清空。前两者的时间复杂度为o(1)。

迭代器

set 和 multiset 的迭代器称为 “ 双向访问迭代器 ”,不支持 “ 随机访问 ”,支持星号解除引用,仅支持 “++” 和 “-- ” 两个与算术相关的操作。

​设 it 是一个迭代器,如 set ::iterator it;

​若把 it++, 则 it 将会指向 “ 下一个 ”元素。 这里的 “ 下一个” 是指在元素从小到大排序的结果中,排在 it
下一名的元素。 同理,若把 it–,则 it 将会指向排在 ”上一个 ”的元素。

​请注意,执行 “ ++ ” 和 ” – “ 操作的时间复杂度都为 o(log n)。
执行操作前后,务必仔细检查,避免迭代器指向的位置超出首,尾迭代器之间的范围。

begin/end

返回集合的首,尾迭代器,时间复杂度为 o(1)。

​ s.begin() 是指向集合中元素最小的迭代器。

​ s.end() 是指向集合中最大元素的下一个位置的迭代器。换言之,就像vector一样,是一个 “ 前闭后开 ”的形式。 因此
–s.end() 是指向集合中最大元素的迭代器。

insert
s.insert(x) 把一个元素 x 插入到集合s中,时间复杂度为 o(log n)
在 set 中,若元素已经存在,则不会重复插入该元素,对集合的状态无影响。
    
	下面的代码把 n 个整数插入有序多重集 multiset,并从小到大输出,时间复杂度为 o (nlog n),相当于进行了一次排序。假设 n 个整数目前存储在数组 a[1~n]中,
    multiset <int> s;
	for(int i=1; i<=n; i++) s.insert(a[i]);
	for(multiset<int>::iterator it = s.begin(),it != s.end(); it++)
        cout<<*it<<endl;

find

s.find(x) 在集合中查找等于 x 的元素,并返回指向该元素的迭代器。若不存在,则返回 s.end()。 时间复杂度为 o (log n)

lower_bound/upeer_bound

这两个函数的用法与find类似,但查找的条件略有不同,时间复杂度o(log n)。

​ s.lower_bound(x) 查找 >= x 的元素中最小的一个,并返回指向该元素的迭代器。

​ s.upper_bound(x) 查找 >x 的元素的最小的一个,并返回指向该元素的迭代器。

erase

​ 设 it 是一个迭代器,s.erase(it) 从 s 中删除迭代器 it 指向的元素,时间复杂度为 o(log n)。

​ 设 x 是一个元素,s.erase(x) 从 s 中删除所有等于 x 的元素,时间复杂度为 o(k + log n),其中 k为被删除的元素的个数。

​ 如果想从 multiset 中删掉至多 1 个等于 x 的元素,可以执行:

​ if ( ( it = s.find(x) ) != s.end() ) s.erase(it);

count

s.count(x) 返回集合中等于 x 的元素个数,时间复杂度为 o(k + log n),其中 k 为元素 x 的个数。

map

map 容器是一个键值对 key-value 的映射。 其内部实现是一颗以 key 为关键码的红黑树。 map 的 key 和 value 可以是任意类型,其中 key 必须定义 “ 小于号 ”运算符。

声明方法为:
    map <key_type, value_type> name;
例如:
    map <long long , bool> vis;
	map <string , int> hash;
	map < pair<int,int>,vector<int> > test;

在很多的时候,map容器被当做 hash 表使用,建立从复杂信息 key (如字符串) 到简单 value (如一定范围内的映射)。

​ 因为map 基于平衡树实现,所以它的大部分操作的时间复杂度都是在 o (log n) 级别,略慢于使用的hash函数实现的传统 hash
表,从 c++11 开始,STL中新增了unordered_map 等基于 hash 的容器,但部分算法竞赛并不支持 c++11
标准,这里就不在介绍这些新容器。

size/empty/clear/begin/end

与set类似,分别为元素个数,是否为空,清空,首迭代器,尾迭代器。

迭代器

​ map的迭代器与 set 一样,也是 “ 双向访问迭代器 ”。对map的迭代器解除引用后,将得到一个二元组 pair<key_type, value_type>.

insert/erase

与set类似,分别插入,删除。insert的参数是pair<key_type, value_type>,erase的参数可以是 key
或者迭代器。

map <int, int> h;
h.insert(make_pair(1,2)), h.insert(make_pair(2,3));
map <int,int>::iterator it = h.begin();
pair<int,int> p = *it;
h.erase(it),h.erase(2);
cout<< p.first << ' ' << p.second <<endl;

find

h.find(x) 在变量名为 h 的map 中查找 key 为 x 的二元组,并返回指向该二元组的迭代器。若不存在,返回 h.end()。
时间复杂度为 o(log n)。

[]操作符

h[key] 返回 key 映射到的 value 的引用,时间复杂度为 o(log n)。

[ ]操作符是map最吸引人的地方。我们可以很方便地通过h[key] 来得到 key 对应的value,还可以对 h[key]
进行赋值操作,改变key对应的value。

​ 需要特别注意的是,若查找的 key 不存在,则执行 h[key] 后, h会自动新建一个二元组(key,value),并返回 zero
的引用。这里 zero 表示一个广义 “ 零值 ”,如整数0,空字符串等。如果查找之后不对 h[key]
进行赋值,那么时间一长,h就会包含很多无用的 “ 零值二元组 ”,白白占用了空间,降低程序运行效率
。强烈建议在用[]操作的时候,先用find方法检查一下key是否存在。

bitset

bitset 可以看成一个多位二进制数,每 8 位占用 1 个字节,相当于采用了状态压缩的二进制数组,并支持基本的位运算。在估算程序运行的时间时,我们一般以 32 位整数的运算次数为基准,因此 n 位 bitset 执行一次位运算的复杂度可视为 n/32,效率较高。

声明
    bitset <10000> s;
	表示一个10000位的二进制数,<>中填写位数。下面把位数记为n。
/*
位运算的操作符
	~s:返回对bitset s 按位取反的结果。
	& ,|,^: 返回对两个位数相同的 bitset 执行按位与,或,异或运算的结果。
	>>,<<:返回把一个 bitset 右移,左移若干位的结果。
	==,!=:比较两个 bitset 代表的二进制数是否相等。
*/
        

[] 操作符

s[k] 表示 s 的第 k 位,既可以取值,也可以赋值。

​ 在 10000位二进制中,最低位为 s[0],最高位为s[9999]。

count

s.count() 返回有多少位为1。

any/none

若 s 所有位都为0,则s.any() 返回false,s.none()返回true。

​ 若 s 至少一位为1,则s.any() 返回true,s.none()返回false。

set/reset/flip

s.set() 把 s 所有位变为1。

​ s.set(k,v) 把 s 的第 k 位改为 v,即 s[k] = v.

​ s.reset() 把 s 所有位都变为0.

​ s.reset(k) 把 s 的第 k 位改为0,即s[k] = 0;

​ s.flip() 把 s 的所有位取反,即 s = ~s;

​ s.flip(k) 把 s 的第 k 位取反, 即 s[k]^=1;

algorithm

下面介绍的几个函数都作用在序列上,接受两个迭代器(或指针)l,r,对下标处于前闭后开区间 [l,r) 中的元素执行一系列操作。

reverse 翻转

翻转一个vector:

​ reverse(a.begin(),a.end());

​ 翻转一个数组,元素存放在下标 1 ~ n:

​ reverse(a+1,a+n+1);

unique去重

​ 返回去重之后的尾迭代器(或指针),仍然为前闭后开,即这个尾迭代器是去重之后末尾元素的下一个位置。该函数常用于离散化,利用迭代器(或指针)的减法,可计算出去重后的元素个数 m。

把一个 vector 去重:

​ int m = unique(a.begin(),a.end()) - a.begin();

​ 把一个数组去重,元素存放在下标 1~n:

​ int m = unique(a+1,a+n+1) - (a+1);

random_shuffle 随机打乱

​用法与reverse相同。

next_permutation 下一个排列

​ 把两个迭代器(或指针)指定的部分看作一个排列,求出这些元素构成的全排列中,字典序排在下一个的排列,并直接在序列上更新,另外,若不存在排名更靠后的false,否则返回true。同理,也有 prev_permutation函数。

​ 下面的程序按字典序输出 1 ~ n的 n! 种全排列:

for(int i=1; i<=n; i++) a[i] = i; 
do {
       for(int i=1; i<n; i++) cout<< a[i] <<" ";
       cout<<a[n]<<endl;
   }while (next_permutation(a+1,a+n+1));

sort 快速排序

​ 对两个迭代器(或指针)指定的部分进行快速排序。可以在第三个参数传入定义大小比较的函数,或者重载 “ 小于号 ” 的运算符。

//把一个int数组(元素存放在下标1~n)从大到小排序,传入比较函数:
int a[max_size];
bool cmp(int a,int b) return a>b;
sort(a+1,a+1+n,cmp);

//把自定义的结构体vector排序,重载 “小于号” 的运算符:
struct rec {int id,x,y;};
vector<rec> a;
bool operator <(const rec&a,const rec&b)
{
    return a.x<b.x || a.x == b.x && a.y <b.y;
}
sort(a.begin(),a.end());
      

lower_bound/upeer_bound 二分

low_bound 的第三个参数传入一个元素 x,在两个迭代器(或指针)指定的部分上执行二分查找,返回指向第一个大于等于 x
的元素的位置的迭代器(或指针)。

​ upper_bound 的用法和 lower_bound 大致相同,唯一的区别就是查找第一个大于 x
的元素。当然,两个迭代器(或指针)指定的部分应该是提前排好序的。

//在有序 int 数组(元素存放在下标 1~n) 中查找大于等于 x 的最小整数的下标:
int i = lower_bound(a+1,a+n+1,x) -a;

//在有序vector<int>中查找小于等于 x 的最大整数(假设一定存在):
int y = *--upper_bound(a.begin(),a.end(),x);
  • 7
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

侧耳倾听QAQ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值