STL (Standard Template Library):是 C++ 标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。

通俗来说:STL 就是将常见的数据结构(例如 顺序表,链表,栈,队列,二叉树,哈希...)以模板的形式进行封装,使用时,不用我们人为再去写,可以直接调用。并且包含常见的通用的泛型算法(一些常规的算法也不用自己实现,可以直接调用)。

STL 六大组件

  • 容器(Container): 各种数据结构,如 vectorlistdequesetmap 等,以模板类的方法提供。为了访问容器中的数据,可以使用由容器类输出的迭代器。

  • 算法(Algorithm): 各种常用算法,提供了执行各种操作的方式,包括对容器内容执行初始化、排序、搜索和转换等操作,如 sortfindcopyerase 函数等。函数本身与它们操作的数据的结构和类型无关,因此它们可以在从简单数组到高度复杂容器的任何数据结构上使用。

  • 迭代器(Iterator): 迭代器提供了访问容器中对象的方法,扮演容器与算法之间的胶合剂,是所谓的“泛型指针”,共有 5 种类型,以及其他衍生变化,是一种将 operator*operator->operator++operator-- 等指针操作予以重载的模板类。所有的 STL 容器附带有自己专属的迭代器,因为只有容器设计者才知道如何遍历自己的元素。

    c++11版本及以后就可以不用迭代器了。

  • 仿函数(Functor): 也称为函数对象(Function object),行为类似函数,可作为算法的某种策略。从实现角度来看,仿函数是一种重载了 operator() 的类或者模板类。

  • 适配器(Adaptor): 一种用来修饰容器、仿函数或者迭代器接口的机制。例如 STL 提供的 queuestack,就是一种空间配接器,因为它们的底部完全借助于 deque

  • 分配器(allocator): 也称为空间配置器,负责空间的配置与管理。从实现的角度来看,配置器是一个实现了动态配置空间、空间管理、空间释放的模板类。

算法 (Algorithm)

头文件:`#include

` ### 常用函数 - **`sort()`** - **功能:** `sort()` 函数可以对给定区间所有元素进行排序。 - **参数:** `sort(begin, end, cmp)` - `begin`:指向待 `sort()` 的数组的第一个元素的指针。 - `end`:指向待 `sort()` 的数组的最后一个元素的下一个位置的指针。 - `cmp`:排序准则,可以不写,默认从小到大进行排序。 ```c++ #include #include using namespace std; int main(){ int num[10] = {6,5,9,1,2,8,7,3,4,0}; // greater () 是一个仿函数,表示降序排列,这里只做演示,是一个伪代码 sort(num, num+10, greater ()); // 排序后 num 变为:9 8 7 6 5 4 3 2 1 0 return 0; } ``` - **`swap()`**交换两个数的值 ```c++ int a = 1, b = 2 ; cout << a << ' ' << b << '\n' ; swap(a, b) ; ``` - **`min()`**取最小值 - **`max()`**取最大值 ### 💡 排序练习 [P5740 【深基7.例9】最厉害的学生 - 洛谷](https://www.luogu.com.cn/problem/P5740) # P5740 【深基7.例9】最厉害的学生 ## 题目描述 现有 $N$ 名同学参加了期末考试,并且获得了每名同学的信息:姓名(不超过 $8$ 个字符的仅有英文小写字母的字符串)、语文、数学、英语成绩(均为不超过 $150$ 的自然数)。总分最高的学生就是最厉害的,请输出最厉害的学生各项信息(姓名、各科成绩)。如果有多个总分相同的学生,输出靠前的那位。 ## 输入格式 第一行输入一个正整数 $N$,表示学生个数。 第二行开始,往下 $N$ 行,对于每一行首先先输入一个字符串表示学生姓名,再输入三个自然数表示语文、数学、英语的成绩。均用空格相隔。 ## 输出格式 输出最厉害的学生。 ## 输入输出样例 #1 ### 输入 #1 ``` 3 senpai 114 51 4 lxl 114 10 23 fafa 51 42 60 ``` ### 输出 #1 ``` senpai 114 51 4 ``` ## 说明/提示 数据保证,$1 \leq N \leq 1000$,姓名为长度不超过 $8$ 的字符串,语文、数学、英语成绩均为不超过 $150$ 的自然数。 --- **自定义 `cmp` 函数实现结构体排序** ```c++ #include using namespace std ; const int N = 1008 ; struct node { string name ; int a, b, c ; int sum ; int num ; } no[N]; // 自定义比较函数 bool cmp(node x, node y) { // 如果总分相同 if (x.sum == y.sum) { // 按学号从小到大 return x.num < y.num ; } // 否则按总分从大到小 return x.sum > y.sum ; } int main() { int n ; cin >> n ; for (int i = 1 ; i <= n ; i ++) { cin >> no[i].name >> no[i].a >> no[i].b >> no[i].c ; no[i].sum = no[i].a + no[i].b + no[i].c ; no[i].num = i ; } // 使用自定义的 cmp 函数进行排序 sort(no + 1, no + n + 1, cmp) ; cout << no[1].name << ' ' << no[1].a << ' ' << no[1].b << ' ' << no[1].c <<'\n' ; return 0 ; } ``` ------ ## 📦 容器 (Container) 容器是存放数据的数据结构,比如链表,数组,队列... 容器都是一个类(用法与结构体相似)。访问类对象内的函数或变量用点运算符 `.`。 ### `vector` (可变长度数组) vector翻译为向量,但是这里使用“变长数组”的叫法更容易理解,也即“长度根据需要而自动改变的数组”。我们知道在C语言中,普通数组大小一旦确定就不能改变了,在做题时有时会碰到只用普通数组会超内存的情况,这时我们就可以使用vector来定义不定长数组节省空间。 #### 定义 ```c++ #include vector name; // 示例: vector v = {1, 2, 3}; ``` #### 常用函数 - `push_back(x)`:在 vector 后面添加一个元素 `x`,时间复杂度 O(1)。 - `pop_back()`:删除 vector 最后一个元素,时间复杂度 O(1)。 - `back()`:返回最后一个元素的值。 - `front()`:返回第一个元素的值。 - `size()`:返回 vector 中元素的个数,时间复杂度 O(1)。 - `clear()`:清空 vector 中的所有元素,时间复杂度 O(N)。 - `insert(it, x)`:向迭代器 `it` 处插入一个元素 `x`,时间复杂度 O(N)。 - `erase(it)`:删除迭代器 `it` 处的元素。 - `erase(first, last)`:删除 `[first, last)` 区间内的所有元素。 #### 迭代器 ```c++ vector v = {1, 2, 3}; vector ::iterator it ; // 传统迭代器遍历 for (it = v.begin() ; it != v.end() ; it ++) { cout << *it << '\n' ; } // C++11 // // // for (auto i : v) { // auto 自动推导类型 cout << i << ' ' ; } ``` #### 练习 [P1567 统计天数 - 洛谷](https://www.luogu.com.cn/problem/P1567) # P1567 统计天数 ## 题目描述 炎热的夏日,KC 非常的不爽。他宁可忍受北极的寒冷,也不愿忍受厦门的夏天。最近,他开始研究天气的变化。他希望用研究的结果预测未来的天气。 经历千辛万苦,他收集了连续 $N(1 \leq N \leq 10^6)$ 天的最高气温数据。 现在,他想知道最高气温一直上升的最长连续天数。 ## 输入格式 第 1 行:一个整数 $N$ 。$1 \leq N \leq 10^6$ 第 2 行:$N$个空格隔开的整数,表示连续 $N$ 天的最高气温。$0 \leq$ 最高气温 $\leq 10^9$ 。 ## 输出格式 1 行:一个整数,表示最高气温一直上升的最长连续天数。 ## 输入输出样例 #1 ### 输入 #1 ``` 10 1 2 3 2 4 5 6 8 5 9 ``` ### 输出 #1 ``` 5 ``` --- ```c++ #include using namespace std ; int main() { int n ; cin >> n ; vector a; for (int i = 1 ; i <= n ; i ++) { int x ; cin >> x ; a.push_back(x) ; } int ans = 1, tmp = 1 ; for (int i = 1 ; i < a.size() ; i ++) { if (a[i] > a[i - 1]) { tmp ++ ; } else { ans = max(ans, tmp) ; tmp = 1 ; } } ans = max(ans, tmp) ; // 别忘了最后一次的比较 cout << ans << '\n' ; return 0 ; } ``` ------ ### `string` (字符串) C++ 在 STL 中加入了 `string` 类型,对字符串常用的需求功能进行了封装,使得操作起来更方便,且不易出错。 #### 头文件 ```c++ #include ``` #### 定义 ```c++ string a = "abcd"; ``` #### 常用函数 - `+=`:字符串拼接,假设有两个字符串`a`和`b`,可用`a+=b`或`a=a+b`来将`b`接到`a`的后面。 - `==`, `!=`, `<`, `<=`, `>`, `>=`:按字典序比较大小。 - `length()` / `size()`:返回字符串长度,O(1)。 - `erase(it)`:删除 `it` 处的字符,`it` 为迭代器。 - `erase(first, last)`:删除 `[first, last)` 区间的字符,`first`, `last` 为迭代器。 - `erase(pos, length)`:`pos` 为开始删除的起始位置,`length` 为删除的字符个数。 - `clear()`:清空字符串 O(1)。 - `substr(pos, len)`:返回从 `pos` 号位置开始、长度为 `len` 的子串,O(len)。 - `find(str2)`:当 `str2` 是 `str` 的子串时,返回其在 `str` 中第一次出现的位置;否则返回 `string::npos`。O(NM)。 - `front()`:返回第一个字符。 - `back()`:返回最后一个字符。 - `push_back(x)`:将字符 `x` 加到字符串后。 - `pop_back()`:删除最后一个字符。 ------ ### `map` (映射) Map (映射) 元素包含两部分(key, value),key 和 value 可以是任意类型。在 map 里面,一个 key 对应一个 value,类似函数里一个 x 对应一个 y。并且在 map 里,key 不能重复,自动按 key 从小到大排序。 #### 定义 ```c++ #include map mp; // 定义了一个 int 映射 int 的 map map mp; // int -> char ``` #### 常见函数 - `insert(make_pair(x, y))`:插入 key 为 `x`,value 为 `y` 的映射。 - `insert({x,y})`:C++11 版本之后,可以用花括号来生成 pair 二元组。 - **遍历 map** ```c++ map::iterator i; for(i = mp.begin(); i != mp.end(); i++) { cout << i->first << ' ' << i->second; // first 是 key, second 是 value } // C++11 // // // for(auto i : mp) { cout << i.first << ' ' << i.second << '\n'; } // C++17 // // // for(auto [a,b] : mp) { // 结构化绑定 cout << a << ' ' << b << '\n'; } ``` #### 练习 [P1125[NOIP2008 提高组] 笨小猴 - 洛谷](https://www.luogu.com.cn/problem/P1125) # P1125 [NOIP 2008 提高组] 笨小猴 ## 题目描述 笨小猴的词汇量很小,所以每次做英语选择题的时候都很头疼。但是他找到了一种方法,经试验证明,用这种方法去选择选项的时候选对的几率非常大! 这种方法的具体描述如下:假设 $\text{maxn}$ 是单词中出现次数最多的字母的出现次数,$\text{minn}$ 是单词中出现次数最少的字母的出现次数,如果 $\text{maxn}-\text{minn}$ 是一个质数,那么笨小猴就认为这是个 Lucky Word,这样的单词很可能就是正确的答案。 ## 输入格式 一个单词,其中只可能出现小写字母,并且长度小于 $100$。 ## 输出格式 共两行,第一行是一个字符串,假设输入的单词是 Lucky Word,那么输出 `Lucky Word`,否则输出 `No Answer`; 第二行是一个整数,如果输入的单词是 Lucky Word,输出 $\text{maxn}-\text{minn}$ 的值,否则输出 $0$。 ## 输入输出样例 #1 ### 输入 #1 ``` error ``` ### 输出 #1 ``` Lucky Word 2 ``` ## 输入输出样例 #2 ### 输入 #2 ``` olympic ``` ### 输出 #2 ``` No Answer 0 ``` ## 说明/提示 【输入输出样例 1 解释】 单词 `error` 中出现最多的字母 $\texttt r$ 出现了 $3$ 次,出现次数最少的字母出现了 $1$ 次,$3-1=2$,$2$ 是质数。 【输入输出样例 2 解释】 单词 `olympic` 中出现最多的字母 $\texttt i$ 出现了 $1$ 次,出现次数最少的字母出现了 $1$ 次,$1-1=0$,$0$ 不是质数。 (本处原题面错误已经修正) noip2008 提高第一题 --- ```c++ #include #define inf 0x3f3f3f3f using namespace std ; map cnt; // 统计每个字符出现的次数 // 判断是否为素数 bool judge(int x) { if (x == 0 || x == 1) { return false ; } for (int i = 2 ; i * i <= x ; i ++) { if (x % i == 0) { return false ; } } return true ; } int main() { string s ; cin >> s ; for (auto i : s) { cnt[i] ++ ; // map可以直接用[]来访问和修改value } int minn = inf, maxn = -1 ; for (auto i : s) { minn = min(minn, cnt[i]) ; maxn = max(maxn, cnt[i]) ; } if (judge(maxn - minn)) { cout << "Lucky Word" << '\n' ; cout << maxn - minn << '\n' ; } else { cout << "No Answer\n0\n" ; } return 0 ; } ``` ------ ### `queue` (队列) 队列是一种**先进先出 (FIFO)** 的数据结构。和现实中的队列一个样,从队尾添加元素,从队头删除元素。 #### 定义 ```c++ #include queue name; ``` #### 常用函数 - `push(x)`:将 `x` 入队(到队尾)。 - `pop()`:将队首元素出队。 - `front()`:返回队首元素。 - `back()`:返回队尾元素。 - `size()`:返回队列元素个数。 - `empty()`:返回队列是否为空。为空时返回 `true`。 #### 练习 [B3616 【模板】队列 - 洛谷](https://www.luogu.com.cn/problem/B3616) # B3616 【模板】队列 ## 题目描述 请你实现一个队列(queue),支持如下操作: - `push(x)`:向队列中加入一个数 $x$。 - `pop()`:将队首弹出。如果此时队列为空,则不进行弹出操作,并输出 `ERR_CANNOT_POP`。 - `query()`:输出队首元素。如果此时队列为空,则输出 `ERR_CANNOT_QUERY`。 - `size()`:输出此时队列内元素个数。 ## 输入格式 第一行,一个整数 $n$,表示操作的次数。 接下来 $n$ 行,每行表示一个操作。格式如下: - `1 x`,表示将元素 `x` 加入队列。 - `2`,表示将队首弹出队列。 - `3`,表示查询队首。 - `4`,表示查询队列内元素个数。 ## 输出格式 输出若干行,对于每个操作,按「题目描述」输出结果。 每条输出之间应当用空行隔开。 ## 输入输出样例 #1 ### 输入 #1 ``` 13 1 2 3 4 1 233 3 2 3 2 4 3 2 1 144 3 ``` ### 输出 #1 ``` 2 1 2 233 0 ERR_CANNOT_QUERY ERR_CANNOT_POP 144 ``` ## 说明/提示 ### 样例解释 首先插入 `2`,队首为 `2`、队列内元素个数为 `1`。 插入 `233`,此时队首为 `2`。 弹出队首,此时队首为 `233`。 弹出队首,此时队首为空。 再次尝试弹出队首,由于队列已经为空,此时无法弹出。 插入 `144`,此时队首为 `144`。 ### 数据规模与约定 对于 $100\%$ 的测试数据,满足 $n\leq 10000$,且被插入队列的所有元素值是 $[1, 1000000]$ 以内的正整数。 --- ```c++ #include using namespace std ; int main() { int n ; cin >> n ; queue q ; while (n --) { int op ; cin >> op ; if (op == 1) { int x ; cin >> x ; q.push(x) ; } else if (op == 2) { if (q.empty()) { cout << "ERR_CANNOT_POP" << '\n' ; } else { q.pop() ; } } else if (op == 3) { if (q.empty()) { cout << "ERR_CANNOT_QUERY" << '\n' ; } else { cout << q.front() << '\n' ; } } else { cout << q.size() << '\n' ; } } return 0 ; } ``` ------ ### `stack` (栈) 栈是一种**先进后出 (LIFO)** 的数据结构,并且只能在栈顶进行插入和删除操作。 #### 定义 ```c++ #include stack name; ``` #### 常用函数 - `push(x)`:将 `x` 压入栈(到栈顶)。 - `pop()`:将栈顶元素弹出。 - `top()`:返回栈顶元素。 - `size()`:返回栈中元素个数。 - `empty()`:返回栈是否为空。 #### 练习 [B3614 【模板】栈 - 洛谷](https://www.luogu.com.cn/problem/B3614) # B3614 【模板】栈 ## 题目描述 请你实现一个栈(stack),支持如下操作: - `push(x)`:向栈中加入一个数 $x$。 - `pop()`:将栈顶弹出。如果此时栈为空则不进行弹出操作,输出 `Empty`。 - `query()`:输出栈顶元素,如果此时栈为空则输出 `Anguei!`。 - `size()`:输出此时栈内元素个数。 ## 输入格式 **本题单测试点内有多组数据**。 输入第一行是一个整数 $T$,表示数据组数。对于每组数据,格式如下: 每组数据第一行是一个整数,表示操作的次数 $n$。 接下来 $n$ 行,每行首先由一个字符串,为 `push`,`pop`,`query` 和 `size` 之一。若为 `push`,则其后有一个整数 $x$,表示要被加入的数,$x$ 和字符串之间用空格隔开;若不是 `push`,则本行没有其它内容。 ## 输出格式 对于每组数据,按照「题目描述」中的要求依次输出。每次输出占一行。 ## 输入输出样例 #1 ### 输入 #1 ``` 2 5 push 2 query size pop query 3 pop query size ``` ### 输出 #1 ``` 2 1 Anguei! Empty Anguei! 0 ``` ## 说明/提示 ### 样例 1 解释 对于第二组数据,始终为空,所以 `pop` 和 `query` 均需要输出对应字符串。栈的 size 为 0。 ### 数据规模与约定 对于全部的测试点,保证 $1 \leq T, n\leq 10^6$,且单个测试点内的 $n$ 之和不超过 $10^6$,即 $\sum n \leq 10^6$。保证 $0 \leq x \lt 2^{64}$。 ### 提示 - 请注意大量数据读入对程序效率造成的影响。 - 因为一开始数据造错了,请注意输出的 `Empty` 不含叹号,`Anguei!` 含有叹号。 --- ```c++ #include #define int unsigned long long // // // using namespace std ; void solve() { stack s ; int n ; cin >> n ; while (n --) { string ss ; cin >> ss ; if (ss == "push") { int x ; cin >> x ; s.push(x) ; } else if (ss == "pop") { if (s.empty()) { cout << "Empty" << '\n' ; } else { s.pop() ; } } else if (ss == "query") { if (s.empty()) { cout << "Anguei!" << '\n' ; } else { cout << s.top() << '\n' ; } } else { // "size" cout << s.size() << '\n' ; } } } signed main() { ios::sync_with_stdio(false) ; // 加速cin cin.tie(0) ; // 解除cin和cout的绑定 int T ; cin >> T ; while (T --) { solve() ; } return 0 ; } ``` ------ ### `set` (集合) Set 集合,是一个内部自动有序且不含重复元素的容器。若插入重复元素,会自动忽略该次插入操作,并且 set 里面的元素自动从小到大排序。 #### 定义 ```c++ #include set name; ``` #### 访问 Set 只能通过迭代器访问。 ```c++ set st; // ... 插入元素 ... // 1. 迭代器 set ::iterator it; for(it = st.begin(); it != st.end(); it++) { printf("%d", *it); // 可以通过 *it 来访问 set 里的元素 } // 2. C++11 // // // for(auto i : st) { printf("%d", i); } ``` #### 常用函数 - `insert(x)`:将 `x` 插入 set 中,并自动递增排序和去重,时间复杂度为 O(logN)。 - `find(x)`:返回 set 中值为 `x` 的迭代器,时间复杂度为 O(logN)。如果找不到,返回 `s.end()`。 - `erase(it)`:`it` 为所需要删除元素的迭代器。时间复杂度为 O(1)。 - `erase(x)`:删除值为 `x` 的元素,时间复杂度为 O(logN)。 - `erase(first, last)`:删除区间 `[first, last)` 内的元素,`first`, `last` 为迭代器。 - `size()`:返回 set 内元素个数。 - `clear()`:清空 set 中的所有元素。 #### 练习 [P1138 第 k 小整数 - 洛谷](https://www.luogu.com.cn/problem/P1138) # P1138 第 k 小整数 ## 题目描述 现有 $n$ 个正整数,要求出这 $n$ 个正整数中的第 $k$ 个最小整数(相同大小的整数只计算一次)。 ## 输入格式 第一行为 $n$ 和 $k$; 第二行开始为 $n$ 个正整数的值,整数间用空格隔开。 ## 输出格式 第$k$个最小整数的值;若无解,则输出 `NO RESULT`。 ## 输入输出样例 #1 ### 输入 #1 ``` 10 3 1 3 3 7 2 5 1 2 4 6 ``` ### 输出 #1 ``` 3 ``` ## 说明/提示 $n \leq 10000$,$k \leq 4000$,正整数均小于 $30000$。 --- 方法一 (使用 set): set 自动排序和去重,非常适合这道题。 ```c++ #include using namespace std ; int main() { set s ; int n, k ; cin >> n >> k ; for (int i = 1 ; i <= n ; i ++) { int x ; cin >> x ; s.insert(x) ; // 自动去重和排序 } if (k > s.size()) { // 如果k大于去重后的元素个数 cout << "NO RESULT" << '\n' ; } else { int pos = 1 ; for (auto i : s) { // 遍历set if (pos == k) { cout << i << '\n' ; break ; } pos ++ ; } } return 0 ; } ``` 方法二 (使用 vector + map 或 bool 数组去重): 手动去重,然后排序。 ```c++ #include using namespace std ; map vis ; // 用map标记是否出现过 int main() { vector a ; int n, k ; cin >> n >> k ; for (int i = 1 ; i <= n ; i ++) { int x ; cin >> x ; if (vis[x]) { // 如果出现过 continue ; } vis[x] = true ; // 标记为出现过 a.push_back(x) ; } if (k > a.size()) { cout << "NO RESULT" << '\n' ; } else { sort(a.begin(), a.end()) ; // 排序 cout << a[k - 1] << '\n' ; // vector下标从0开始,第k个是 a[k-1] } return 0 ; } ```
计算机小白一枚
最后更新于 2025-11-16