STL 简介

每一个版本的 C++ 标准不仅规定了 C++ 的语法、语言特性,还规定了一套 C++ 内置库的实现规范,这个库便是 C++ 标准库。C++ 标准库中包含大量常用代码的实现,如输入输出、基本数据结构、内存管理、多线程支持等。

STL 即标准模板库(Standard Template Library),是 C++ 标准库的一部分,里面包含了一些模板化的通用的数据结构和算法。由于其模板化的特点,它能够兼容自定义的数据类型,合理利用 STL 可以避免编写无用算法,省去大量的造轮子的工作。

STL 标准模板库从广义上讲分为 算法(Algorithm)、容器(Container)迭代器Iterator3 类。

算法(Algorithm)

在头文件 <algorithm> 中,封装好了许多可直接调用的常用操作的函数,例如排序 sort() ,二分查找 binary_search() 。

算法作用于容器,它提供了对容器内容进行初始化、排序、搜索和转换等常用操作的实现方式,使用 STL 中的算法就不必自己造轮子了。

常用算法

元素相关

求最小值:min(x, y)

求两个元素的最小值。

求最大值:max(x, y)

求两个元素的最大值。

交换元素:swap(x, y)

交换两个元素。

示例程序

    int a = 55, b = 23;
    printf("min: %d\n", min(a, b)); // min: 23
    printf("max: %d\n", max(a, b)); // max: 55
    swap(a, b);
    printf("After swap: a = %d, b = %d\n", a, b);
    // After swap: a = 23, b = 55

区间相关

注:begin 表示区间起点,end 表示区间终点,区间为左闭右开 (端点 >= begin 且 < end)。例如示例程序区间,begin 为 a (数组名也表示数组首元素地址),end 为 a + 15(有 15 个数)。

    int a[20] = {0, 1, 1, 2, 3, 5, 0, 7,
                  6, 9, 6, 0, 3, 5, 8};

查找:find(begin, end, x)

返回区间 [begin,end) 中第 1 个值为 x 的元素地址。

若不存在值为 x 的元素查找失败,则返回 end(终点地址)。

    int *p = find(a, a + 15, 8); // p 存查找后的返回地址
    if(p != a + 15) // 不是尾地址,则有找到
        cout << find(a, a + 15, 6) - a; // 返回地址值减首地址就是下标
    else
        cout << "not found";

计数:count(begin, end, x)

统计区间 [begin,end) 种值为 x 的元素数量,返回值为整数。

    printf("cnt(3): %d\n", count(a, a + 15, 3)); // 2
    printf("cnt(0): %d\n", count(a, a + 15, 0)); // 3
    printf("cnt(4): %d\n", count(a, a + 15, 4)); // 0

翻转:reverse(begin, end)

翻转区间 [begin,end) 中的元素序列。

    reverse(a, a + 10); //  翻转 a[0] ~ a[9]
    for(int i = 0; i < 15; i++) cout << a[i] << " ";
    cout << "\n";
    // 前 10 个元素变成:9 6 7 0 5 3 2 1 1 0,后 5 个不变

填充:fill(begin, end, x)

将区间 [begin,end) 中的每个元素值都设置为 x。

    fill(a, a + 10, 3); // 将 a[0] ~ a[9] 都设为 3
    for(int i = 0; i < 15; i++) cout << a[i] << " ";
    cout << "\n";
    // 输出:3 3 3 3 3 3 3 3 3 3 6 0 3 5 8

排序相关

排序:sort(begin, end, compare)

begin 和 end 分别为待排序数组的首地址和尾地址;compare 表示排序的比较函数,可省略不填。

默认为升序,若要降序,可在第三个参数处填入 greater<int>()

sort 排序采用的是成熟的“快速排序算法”,可以保证很好的平均性能,其时间复杂度为 nlog(n)。

稳定排序:stable_sort (begin, end, compare)

stable_sort() 能保持相等元素的相对顺序在排序前后一致。即使原来相同的值在序列中的相对位置不变。例如初始序列中有两个元素 A 和 B 的值相等,且 A 排在 B 的前面,排序后 A 仍然排在 B 的前面,这种排序叫作稳定排序。

去重:unique(begin, end)

将区间 [begin,end) 中的连续相同元素进行去重,返回去重后的尾指针。

不连续的相同元素不会被压缩去重,因此一般先排序后去重

示例程序

    sort(a, a + 15); // 对 a[0] ~ a[14] 元素排序
    int k = unique(a, a + 15) - a; // 对连续相同元素去重,减 a 去重后元素个数
    printf("distinct numbers: %d\n", k); // distinct numbers: 9
    for(int i = 0; i < k; i++) cout << a[i] << " ";
    // 输出:0 1 2 3 5 6 7 8 9
    cout << "\n";
    sort(a, a + k, greater<int>()); // greater<>() 表示降序排序
    for(int i = 0; i < k; i++) cout << a[i] << " ";
    // 输出:9 8 7 6 5 3 2 1 0
    cout << "\n";

二分查找

注:begin 表示区间起点,end 表示区间终点,区间为左闭右开 (端点 >= begin 且 < end)。

二分查找:binary_search(begin, end, x)

判断 x 是否在 [begin,end) 里,如果存在则返回 true,否则返回 false。

查找左边界:lower_bound(begin, end, x)

在一个有序序列 [begin,end) 中进行二分查找,返回第一个 大于等于 x 的元素的地址。

如果区间内所有元素都小于 x,则返回 end 的地址,且 end 的地址是越界的。

查找右边界:upper_bound(begin, end, x)

在一个有序序列 [begin,end) 中进行二分查找,返回第一个 大于 x 的元素的地址。

如果区间内所有元素都小于 x,则返回 end 的地址,且 end 的地址是越界的。

图解

特别注意

  1. lower_bound 和 upper_bound 二分查找的区间必须为有序序列
  2. 由于 lower_bound 和 upper_bound 返回的是数组元素的地址值,所以用它减去数组首地址的值就是该数组元素的下标,即 lower/upper_bound(a, a + n, x) – a 可以表示查找到的下标。
  3. 降序序列使用要注意以下两点:
    1. lower_bound 的正确写法为 lower_bound(begin, end, x, greater<int>()),或类似于 sort 排序,使用自定义比较函数。若 x 在序列中,则返回 x 第一次出现的位置,否则返回第一个插入 x 不影响原序列顺序的位置。
    2. upper_bound 的正确写法为 upper_bound(begin, end, x, greater<int>()),或类似于 sort 排序,使用自定义比较函数。若 x 在序列中,则返回第一个小于 x 的位置,否则返回第一个插入 x 不影响原序列顺序的位置。

排列

下个排列:next_permutation(begin, end)

将当前排列更改为全排列中的下一个排列

注:如果当前排列已经是全排列中的最后一个排列(元素完全从大到小排列),函数返回 false,并将排列更改为全排列中的第一个排列(元素完全从小到大排列);否则,函数返回 true。

上个排列:pre_permutation(begin, end)

将当前排列更改为全排列中的上一个排列。用法同 next_permutation()。

经典例题

Bookshelf B

思路

使用 sort(a, a + n, greater<int>()) 逆序排序

代码

#include <bits/stdc++.h>
using namespace std;


int n, h[20005], s, b, i;
int main(){
    scanf("%d %d", &n, &b);
    for(int i = 1; i <= n; i++)
        scanf("%d", &h[i]);
    sort(h + 1, h + 1 + n, greater<int>());
    for(i = 1; s < b; i++)
        s += h[i]; // 循环结束时 i 为第一个不满足条件处
    printf("%d", --i);
    return 0;
} 

不重复地输出数

思路

使用 sort() 和 unique()

代码

#include <bits/stdc++.h>
using namespace std;


int n, a[100005];
int main(){
    scanf("%d", &n);
    for(int i = 0; i < n; i++)
        scanf("%d", &a[i]);
    sort(a, a + n);
    // 返回去重后最后一个元素地址,减首地址为元素个数
    int k = unique(a, a + n) - a;
    for(int i = 0; i < k; i++)
        printf("%d ", a[i]);
    return 0;
} 

查找

思路

使用 lower_bound()

代码

#include <bits/stdc++.h>
using namespace std;


int n, m, a[1000005], q;
int main(){
    scanf("%d %d", &n, &m);
    for(int i = 0; i < n; i++){
        scanf("%d", &a[i]);
    }
    while(m--){
        scanf("%d", &q);
        int p = lower_bound(a, a + n, q) - a;
        if(a[p] == q)
            printf("%d ", p + 1);
        else
            printf("-1 ");
    }
    return 0;
} 

和为给定数

思路

使用 lower_bound()

代码

#include <bits/stdc++.h>
using namespace std;

int n, a[100005], m;
int main(){
    scanf("%d", &n);
    for(int i = 0; i < n; i++){
        scanf("%d", &a[i]);
    }
    scanf("%d", &m);
    sort(a, a + n);
    for(int i = 0; i < n; i++){
        // [i+1, n-1] 查找 m - a[i]
        int j = lower_bound(a + i + 1, a + n, m - a[i]) - a;
        if(a[i] + a[j] == m){
            printf("%d %d\n", a[i], a[j]);
            return 0;
        }
    }
    printf("No");
    return 0;
} 

A-B 数对

思路

使用 lower_bound() / upper_bound()

代码

#include <bits/stdc++.h>
using namespace std;

int n, a[200005], c;
long long s;
int main(){
    scanf("%d %d", &n, &c);
    for(int i = 0; i < n; i++){
        scanf("%d", &a[i]);
    }
    sort(a, a + n);
    for(int i = 0; i < n; i++){ // 枚举较小数 a[i]
        // 在 i 后面 [i + 1, n) 查找较大数 a[i] + c
        int t = upper_bound(a + i + 1, a + n, a[i] + c)  // 第一个大于 a[i] + c
                 - lower_bound(a + i + 1, a + n, a[i] + c); // 第一个大于等于 a[i] + c
        s += t;
    }
    printf("%lld", s);
    return 0;
} 

全排列问题

思路

使用 next_permutation()

代码

#include <bits/stdc++.h>
using namespace std;

int n, a[10005];
int main(){
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
        a[i] = i;
   do{
        for(int i = 1; i <= n; i++)
            printf("%5d ", a[i]);
        printf("\n");
   }while(next_permutation(a + 1, a + 1 + n));
    return 0;
} 

火星人

思路

使用 next_permutation()

代码

#include <bits/stdc++.h>
using namespace std;

int n, m, a[10005];
int main(){
    scanf("%d %d", &n, &m);
    for(int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    while(m--){ // 循环 m 次生成 a[] 的下个排列
        next_permutation(a + 1, a + 1 + n);
    }
    for(int i = 1; i <= n; i++)
        printf("%d ", a[i]);
    return 0;
} 

数组元素循环右移问题

思路

使用 reverse()。循环移位 m 位的三次翻转算法:

  1. 翻转 a(1)……a( n- m)
  2. 翻转 a(n – m + 1)……a(n)
  3. 翻转 a(1) ~ a(n)
#include <bits/stdc++.h>
using namespace std;

int n, a[105], m;
int main(){
    cin >> n >> m;
    m %= n; // 移位大于 n 的情况
    for(int i = 1; i <= n; i++)
        cin >> a[i];
    reverse(a + n - m + 1, a + 1 + n); // 翻转右半部分
    reverse(a + 1, a + n - m + 1); // 翻转左半部分
    reverse(a + 1, a + 1 + n); // 整体再翻转
    for(int i = 1; i <= n; i++)
        cout <<  a[i] << (i < n ? " " : "");
    return 0;
} 

导弹拦截

思路

先假设都使用系统 1,对每颗导弹根据到系统 1 的距离从小到大排序,再尝试加入系统 2,枚举所有可能的点计算总距,取所有方案中的最小值。

代码

#include <bits/stdc++.h>
using namespace std;

struct B{
    int d1, d2;
};

bool cmp(B b, B c){ // 第一套系统距离小到大排序
    return b.d1 < c.d1;
}

int main(){
    B a[100005];
    int x_1, y_1, x_2, y_2, x, y, n;
    scanf("%d %d %d %d %d", &x_1, &y_1, &x_2, &y_2, &n);
    for(int i = 1; i <= n; i++){
        scanf("%d %d", &x, &y);
        a[i].d1 = pow(x - x_1, 2) + pow(y - y_1, 2); // 系统 1 距离
        a[i].d2 = pow(x - x_2, 2) + pow(y - y_2, 2); // 系统 2 距离
    }
    sort(a + 1, a + 1 + n, cmp);
    // 求 a[i].d2 的后缀最大值
    a[0].d1 = 0; // 初始化系统 1 不用的情况
    int max_d2 = 0, mins = a[n].d1; // 全用系统 1
    for(int i = n; i >= 1; i--){ // 从 a[i ~ n] 用系统 2
        max_d2 = max(max_d2, a[i].d2);  // a[i ~ n] 中的 d2 最大值
        // 算总距:a[1] ~ a[i-1] 系统 1 拦, a[i] ~ a[n] 系统 2 拦
        mins = min(mins, a[i-1].d1 + max_d2);
    }
    printf("%d", mins);
    return 0;
} 

拓展资料

  1. OI Wiki – C++ 标准库
  2. algorithm下的常用函数-习题集

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注