【C++】常用STL浅析


End of The End

前置芝士

模板类

一般来说,有两种方式来创造模板类
template<class T>template<typename T>

至于这两种有什么区别呢?
首先,模板类都是用在函数或结构体中

  • 对于 t e m p l a t e &lt; c l a s s   T &gt; template&lt;class\ T&gt; template<class T>来说,一般用在有参数的函数当中,例如以下代码中的 w r i t e write write, M i n Min Min, M a x Max Max, A b s Abs Abs
  • 对于 t e m p l a t e &lt; t y p e n a m e   T &gt; template&lt;typename\ T&gt; template<typename T>来说,一般用在没有参数的函数和结构体当中,例如以下代码中的 r e a d read read

至于结构体,给一个pair的原型

template<typename T1,typename T2>
struct Pair{
    T1 first; T2 second;
    inline Pair(int f=0,int s=0) { first=f,second=s; }
    inline bool operator < (const Pair & b) const {
        return first<b.first;
    }
};
Pair<int,int>pii;

发现了吗? t e m p l a t e template template不只可以有一个参数,也可以有多个!
同理, t e m p l a t e &lt; c l a s s . . . &gt; template&lt;class ...&gt; template<class...>也可以多个参数,例如

template<class T1,class T2>
inline void Add(T1&a,T2 b) {
    a=a+b;
}

于是,可以有一些常见的函数可以这样写

template <typename T>
inline T read() {
    T a=0; char c=getchar(),f=1;
    while(c<'0'||c>'9') {
        if(c=='-') f=-f;
        if(c==-1) return c;
        c=getchar();
    }
    while(c>='0'&&c<='9') a=(a<<1)+(a<<3)+(c^48),c=getchar();
    return a*f;
}//快读
template <class T>
inline int write(T x) {
    if(x<0) x=(~x)+1, putchar('-');
    if(x/10) write(x/10);
    return putchar(x%10|48);
}//快写
template <class T>
inline T Max(T a,T b) { return a>b?a:b; }
template <class T>
inline T Min(T a,T b) { return a<b?a:b; }
template <class T>
inline T Abs(T a) { return a<0?-a:a; }

指针

C++里还有一个东西叫指针,是一种对象。指针在本质上是一个地址,因此指针的赋值需要用取地址符&。
获取指针指向的地址有两种方式,这里先介绍一种,用 ∗ * 指针名来获取指针指向的地址的变量的一个引用。举个例子:

int i = 25;
int *x = &i;
int y = *x;
(*x) = 6;

运行完上述代码后, i i i的值变成了6。
指针一般有三种形式:

  1. 指向一个对象

  2. 空指针

  3. 无效指针

空指针即指针值为NULL,这个东西定义在头文件cstdlib中。如果指针未被初始化或者指向对象的空间被回收等等则该指针为无效指针。我们应尽量避免出现无效指针,这往往会让你的代码出现错误而且难以调试。
指针可以使用加减运算符,表示向前或向后任意单位长度的对象的地址。
因为指针是对象,所以可以有指针的指针,指针的指针称为二级指针,二级指针的指针称为三级指针,三级指针的指针称为四级指针,以此类推。指针的功能十分强大,但也难以调试,很多程序员往往会在调试指针上花费大量时间。

好了,我讲完了

queue

头文件:#include<queue>+using namespace std
queue,一个 F I F O ( F i r s t   I n   F i r s t   O u t ) FIFO(First\ In\ First\ Out) FIFO(First In First Out)队列
队列符合一个原则,就是 F I F O ( F i r s t   I n   F i r s t   O u t ) FIFO(First\ In\ First\ Out) FIFO(First In First Out)(先进先出)
队列使用模板类来实现不同数据结构的队列,如下
函数表

函数名时间复杂度
push(x) O ( 1 ) O(1) O(1)
pop() O ( 1 ) O(1) O(1)
size() O ( 1 ) O(1) O(1)
empty() O ( 1 ) O(1) O(1)
#include<queue>
using namespace std;
queue<int>q;
queue<double>Q;
queue<char>cq;
queue<queue<int> >qq;
void solve() {
    q.push(1);
    q.push(2147483647);
    Q.push(2.13);
    cq.push('o');
    cq.push('m');
    cq.push('g');
    while(!cq.empty()) {
        printf("%d %c\n",cq.size(),cq.front());
        cq.pop();
    }
    qq.push(q);
}

输出

3 o
2 m
1 g

队列是可以实现 b f s bfs bfs的,不过手写有时会出错,不如用STL—queue,对于一个邻接表而言,代码如下(没看懂vector不要急,后面有讲解)

//对于无权图
#include<queue>
#include<vector>
using namespace std;
const int MAXV=10001;
vector<int>G[MAXV];
bool vis[MAXV];
void bfs(int start_node) {
    queue<int>q;//queue做局部变量可自动清零
    q.push(start_node);
    vis[start_node]=1;
    while(!q.empty()) {
        int t=q.front();//队头
        q.pop();//从该元素拓展,该元素出队
        for(int i=0;i<int(G[t].size());i++) {
            if(!vis[G[t][i]]) {
                q.push(G[t][i]);//遍历到的元素入队
                vis[G[t][i]]=1;
            }
        }
    }
}

deque

#include<queue>+using namespace std#include<deque>+using namespace std
d e q u e deque deque,即双向队列,就是可以从双端进出的队列
函数表

函数名时间复杂度
push_back(x) O ( 1 ) O(1) O(1)
push_front(x) O ( 1 ) O(1) O(1)
pop_back() O ( 1 ) O(1) O(1)
pop_front() O ( 1 ) O(1) O(1)
size() O ( 1 ) O(1) O(1)
empty() O ( 1 ) O(1) O(1)
#include<deque>
#include<string>
using namespace std;
deque<string>dq;
void solve() {
    dq.push_back("Catalan");
    dq.push_back("Fibonacci");
    dq.push_front("Stirling");
    dq.push_front("DP");
    while(!dq.empty()) {
        puts(dq.front().c_str());
        printf("%d\n",dq.size());
        dq.pop_front();
        puts(dq.back().c_str());
        printf("%d\n",dq.size());
        dq.pop_back();
    }
}

输出

DP
4
Fibonacci
3
Stirling
2
Catalan
1

d e q u e deque deque可以用于实现单调队列(不懂请左转我的这篇博客),如下(以单调下降为例)

typedef deque<int> dqi;
//这句话相当于#define dqi deque<int>,只是用typedef写出来更符合C++标准
dqi dq;
inline void push_back(dqi q,int val) {
    while(val<dq.back()) dq.pop_back();
    dq.push_back(val);
}
inline void push_front(dqi q,int val) {//还可以实现手动模拟做不到的在前面插入
    while(val>dq.front()) dq.pop_front();
    dq.push_front(val);
}

priority_queue

头文件:#include<queue>+using namespace std
可以很轻松的直译过来, p r i o r i t y _ q u e u e priority\_queue priority_queue就是优先队列的意思。
意思就是,这个队列里每一个元素都是有序的,但不像二叉搜索树那样,它是有队列的操作的,函数如下

函数名时间复杂度
push(x) O ( l o g n ) O(logn) O(logn)
pop() O ( l o g n ) O(logn) O(logn)
size() O ( 1 ) O(1) O(1)
empty() O ( 1 ) O(1) O(1)

对于优先队列这个东西,还有一种很666的东西
想一想,sort都可以自定义cmp进行排序,那优先队列可以吗?
答案是肯定的,可以用结构体实现,不过比较复杂,这里就不介绍了
值得一提的是,优先队列的初始化是大根堆,就是头元素最大,那如何变成小根堆呢?
如下

priority_queue<int>q;//大根堆
priority_queue<int,vector<int>,greater<int> >Q;//小根堆

至于为什么要打一个空格,因为有一些编译器会把>>理解成右移符号。
就发一个堆优化 D i j k s t r a Dijkstra Dijkstra吧,时间复杂度 O ( E   l o g V ) O(E\ logV) O(E logV)

#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
int read()
{
    int a=0,f=1; char c=getchar();
    while(c>'9'||c<'0') { if(c=='-') f=-f; c=getchar(); }
    while(c>='0'&&c<='9') a=a*10+c-'0',c=getchar();
    return a*f;
}
struct node{
    int u,w;
    node(int U=0,int W=0) { u=U,w=W; }
    bool operator < (const node & b) const {
        return w>b.w;
    }
};
int n=read(),m=read(),S=read();
vector<node>G[100001];
void addEdge(int u,int v,int w) { G[u].push_back(node(v,w)); }
void Dijkstra(int S)
{
    int dis[100001];
    for(int i=1;i<=n;i++) dis[i]=0x7fffffff;
    dis[S]=0;
    priority_queue<node>q;
    q.push(node(S,0));
    while(!q.empty())
    {
        node t=q.top(); q.pop();
        if(t.w!=dis[t.u]) continue;
        int siz=G[t.u].size();
        for(int i=0;i<siz;i++)
        {
            node t1=G[t.u][i];
            if(dis[t1.u]>dis[t.u]+t1.w)
            {
                dis[t1.u]=dis[t.u]+t1.w;
                t1.w=dis[t1.u];
                q.push(t1);
            }
        }
    }
    for(int i=1;i<=n;i++)
        printf("%d ",dis[i]);
}
int main()
{
    for(int i=1;i<=m;i++)
    {
        int u=read(),v=read(),w=read();
        addEdge(u,v,w);
    }
    Dijkstra(S);
}

stack

stack,和队列刚好相反,是一个符合 F ( i r s t )   I ( n )   L ( a s t )   O ( u t ) F(irst)\ I(n)\ L(ast)\ O(ut) F(irst) I(n) L(ast) O(ut)
一看到栈,没错,这东西可以实现单调栈,但是上面deque已经讲了一些,这里就不多赘述。
附: d f s   w i t h   s t a c k dfs\ with\ stack dfs with stack+邻接表

bool vis[MAXN];
inline void dfs(int S) {
    stack<int>s;
    s.push(S); vis[S] = 1;
    while(!s.empty()) {
        int t = s.top(); s.pop();
        for(reg int i = 0; i <= int(G[t].size()); i++) {
            if(!vis[G[t][i]]) {
                vis[G[t][i]] = 1;
                s.push(G[t][i]);
            }
        }
    }
}

vector

发现好像vector应该写在第一个(因为那该死的邻接表)

函数名时间复杂度
push_back(x) O ( 1 ) O(1) O(1)
pop_back() O ( 1 ) O(1) O(1)
size() O ( 1 ) O(1) O(1)
empty() O ( 1 ) O(1) O(1)
begin() O ( 1 ) O(1) O(1)
end() O ( 1 ) O(1) O(1)
insert(p, x) O ( n ) O(n) O(n)
erase(p, x) O ( n ) O(n) O(n)
clear() O ( n ) O(n) O(n)

啊,vector又慢函数又多
你会惊奇地发现多了一个_back几个函数——
clear,就是清空整个vector的
begin,end返回的是一个迭代器,即头指针 和 尾指针的下一个位置(注意断句)
insert,erase中,x是一个值,p是一个迭代器,即插入在哪一个位置之后和删除哪一个位置的数。
迭代器是什么?就是一种指针。只不过定义方法不同罢了,例如一个 v e c t o r &lt; i n t &gt; vector&lt;int&gt; vector<int>的迭代器如下

vector<int>v;
vector<int>::iterator it = v.begin();
//当然,也可以像指针那样取值
printf("%d", *it);

所以,迭代器除了这个,还有何用?
遍历。

for(register vector<int>::iterator it = v.begin(); it != v.end(); it++)
    printf("%d ", *it);
//下面的作用和上面是一样的
for(register int i = 0; i < v.size(); i++)
    printf("%d ", v[i]);

所以写的这么繁琐有什么用?调用省了一个字节。
C++11:看好了!!

for(register auto it = v.begin(); it != v.end(); it++)
    printf("%d ", *it);

有没有好多了?
C++11:继续看我自我推翻!!

for(register int i : v)
    printf("%d ", i);
//这种方法也有不好的地方,就是只能遍历全部

好吧,在vector里面好像确实没啥用
额,只要了解vector怎么实现邻接表就好
如下为一个有向有权图:

struct edge{
    int v, w;
    edge(int V = 0, int W = 0) { v = V; w = W; }
};
vector<edge>G[1001];
inline void addEdge(int u, int v, int w) {
    G[u].push_back(edge(v, w));
}

d f s dfs dfs b f s bfs bfs就看上面吧……

list

这东西真没啥用……天生STL傻常数大

函数名时间复杂度
push_back(x) O ( 1 ) O(1) O(1)
pop_back() O ( 1 ) O(1) O(1)
push_front(x) O ( 1 ) O(1) O(1)
pop_front() O ( 1 ) O(1) O(1)
size() O ( 1 ) O(1) O(1)
empty() O ( 1 ) O(1) O(1)
begin() O ( 1 ) O(1) O(1)
end() O ( 1 ) O(1) O(1)
insert(p, x) O ( 1 ) O(1) O(1)
erase(p, x) O ( 1 ) O(1) O(1)
clear() O ( n ) O(n) O(n)

OMG ~ 怎么这么多O(1)?(别想了,都是假的,STL傻常数大啊)
list是链表啊!
附上:list强行使用迭代器…… Q A Q QAQ QAQ

set

是一棵红黑树,就是二叉搜索树。
拿来处理没有重复关系的集合——就是每一个元素只有一个。
强行使用迭代器…… Q A Q QAQ QAQ
人家好用,比list强多了怎么你不服
注意,p代表的是迭代器

函数名时间复杂度
size() O ( 1 ) O(1) O(1)
empty() O ( 1 ) O(1) O(1)
begin() O ( 1 ) O(1) O(1)
end() O ( 1 ) O(1) O(1)
insert(x) O ( l o g n ) O(logn) O(logn)
erase(x) O ( l o g n ) O(logn) O(logn)
erase(p) O ( l o g n ) O(logn) O(logn)
erase(lp, rp) O ( l o g n ) O(log n) O(logn)
count(x) O ( l o g n ) O(logn) O(logn)
find(x) O ( l o g n ) O(logn) O(logn)
clear() O ( n ) O(n) O(n)

count(x),就是查找含有当前值的元素的个数
erase,一个指针参数的是删除那个位置的数,一个数参数的是删除那个数,两个指针参数的是删除该处于(开)区间位置的数

其实set也有lower_bound和upper_bound,用法和普通的一样,复杂度也一样。其实直接 l o w e r _ b o u n d ( s . b e g i n ( ) , s . e n d ( ) , x , c m p ) lower\_bound(s.begin(), s.end(), x, cmp) lower_bound(s.begin(),s.end(),x,cmp)就好了嘛

//自动打开C++11
set<int>s;
for(register auto it = s.begin(); it != s.end(); it++)
    printf("%d ", *it);
for(register int i : s)
    printf("%d ", i);

栗子……好像想不到什么了……

map

map,就是地图映射,一个值对另一个值。这跟hash好像没啥区别吧……差不多,但是,map有它的独到之处啊。
强行使用迭代器…… Q A Q QAQ QAQ
人家也好用,比list强多了怎么你不服

函数名时间复杂度
size() O ( 1 ) O(1) O(1)
empty() O ( 1 ) O(1) O(1)
begin() O ( 1 ) O(1) O(1)
end() O ( 1 ) O(1) O(1)
insert(PAIR) O ( l o g n ) O(logn) O(logn)
erase(x) O ( l o g n ) O(logn) O(logn)
erase(p) O ( l o g n ) O(logn) O(logn)
erase(lp, rp) O ( l o g n ) O(log n) O(logn)
count(x) O ( l o g n ) O(logn) O(logn)
find(x) O ( l o g n ) O(logn) O(logn)
clear() O ( n ) O(n) O(n)

额,看懂了吗——map的insert的参数是一个pair!
具体来说,看下面吧(注意只是一个伪代码,编译都过不了)

template<typename Type,typename tYPE>
map<Type,tYPE>m;
Type a; tYPE b;
m.insert(make_pair(a, b));
m.erase(a);//删除的是“下标”对应的值

和set一样,map也是强制迭代器操作……
好吧,既然都这样了,map有什么特别呢?就是上码中所说的“下标”。如下:

map<double, string> m;
m[1.0] = "NOIPpj";
m[1.23456789] = "NOIPtg";
m[1e15] = "NOI";
m[1e300] = "CCF";
m[1e-10] = "IOI";
if(m.find(1e-9) == m.end()) m[1e-9] = "C++";
if(m.find(1e-9) == m.end()) m[1e-9] = "error";
else puts(m[1e-9]);
//C++11 ism coming!
for(register auto i : m) {
    cout << i.first << ' ' << i.second << endl;
}

结果:

C++
1e-010 IOI
1e-009 C++
1 NOIPpj
1.23457 NOIPtg
1e+015 NOI
1e+300 CCF

呃呃呃,setw我是真的不会用
这里教大家一个小秘诀,可以查看一个量的类型,如下

template<typename T>
void getKind(const T&x) {
    system((string("c++filt -t ")+typeid(x).name()).c_str());
}

改动一下,变成这样

for(register auto i : m) {
    cout << i.first << ' ' << i.second << endl;
    getKind(i);
}

输出结果(小黑板方式):

C++
1e-010 IOI
std::pair<double const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >
1e-009 C++
std::pair<double const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >
1 NOIPpj
std::pair<double const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >
1.23457 NOIPtg
std::pair<double const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >
1e+015 NOI
std::pair<double const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >
1e+300 CCF
std::pair<double const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >

map的每一个元素都是一个pair!

我们可以同时发现,输出时,所有数据是按照从小到大的顺序输出的——这也是二叉排序树的特点了。


待我学了multiset和multimap再说……

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值