C++容器算法

容器算法

<algorithm> 是 C++ 标准库自带的算法头文件,提供一系列实用的算法。谈到容器算法,大概率会用到谓词(predicate):谓词的返回类型是布尔类型(bool),可以是 lambda 表达式、函数对象以及其它可调用对象。在标准库实现中,predicate 是一元谓词,compare 是二元谓词。

查找

  1. find() 查找元素

    find 接受三个参数,第三个参数是要查找的值;setmap 自带的 count 函数也能实现类似功能,返回 0 表示不存在。为了方便理解,本次将 find_iffind_if_notfind_first_offind_endadjacent_find 一起举例。

    vector<int> a{1, 1, 2, 3, 4, 4, 5};
    vector<int> b{5, 6, 6};
    find(a.begin(), a.end(), 3); // 返回的是迭代器,未查找到返回 a.end()
    find_if(a.begin(), a.end(), [](auto x){return x != 3;}); // 接受一个一元谓词,找到第一个满足条件的元素
    find_if_not(a.begin(), a.end(), [](auto x){return x != 3;}); // 接受一个一元谓词,找到第一个不满足条件的元素
    
    // find_first_of:找到第一个范围中存在于第二个范围的第一个元素(可能并不是第二个范围的第一个元素),返回迭代器,也支持一个二元谓词
    vector<int> a{1, 1, 2, 3, 4, 4, 5};
    vector<int> b{6, 5, 6};
    auto it = find_first_of(a.begin(), a.end(), b.begin(), b.end());
    cout << *it << endl; // 返回 5
    
    // find_end:找到最后一个匹配子序列的位置,返回最后一个子序列开始的迭代器,也支持一个二元谓词
    vector<int> a{1, 1, 2, 3, 1, 4, 1, 2};
    vector<int> b{1, 2};
    auto it = find_end(a.begin(), a.end(), b.begin(), b.end());
    cout << distance(a.begin(), it) << endl; // 6,指向第 7 个元素
    
    // adjacent_find:找到第一对相邻且满足条件的元素(默认是相等),返回指向第一个元素的迭代器,也支持二元谓词
    vector<int> a{1, 1, 2, 3, 4, 1, 2};
    auto it1 = adjacent_find(a.begin(), a.end()); // 找到第一对相邻相等的元素,指向第一个 1
    auto it2 = adjacent_find(a.begin(), a.end(),
        [](auto a, auto b)->bool{return a + b == 5;}); // 二元谓词:找到第一对和为 5 的相邻元素,指向 2
    cout << *it1 << " " << *it2 << endl;
    
  2. all_of() 基于 find_if_not 实现,支持一个一元谓词。

    all_of(a.begin(), a.end(), [](auto x){return x != 3;}); // 判断是否所有元素都满足条件,返回 bool
    
  3. any_of()none_of() 都基于 find_if() 实现。

    none_of(a.begin(), a.end(), [](auto x){return x != 3;}); // 判断是否所有元素都不满足条件
    any_of(a.begin(), a.end(), [](auto x){return x != 3;}); // 只要有一个元素满足条件就返回 true
    

去重

  1. unique(nums.begin(), nums.end()) 去除连续相同的值

    unique 的作用是删除容器中连续相同的值,还支持传入二元谓词(接受两个参数的函数)。它返回一个迭代器 it,指向去重后序列的末尾,但并不是 end()itend() 之间是未指定的值,访问可能产生未定义行为。注意不要对 setmap 使用 unique,它们返回的是常量迭代器,无法修改其指向的元素。

    // 无谓词写法
    vector<int> a{1, 1, 2, 3, 4, 4, 5};
    auto it = unique(a.begin(), a.end()); // 此时 a 的前段为 {1, 2, 3, 4, 5}
    for_each(a.begin(), a.end(), [](auto it){cout << it << endl;}); // 输出 {1, 2, 3, 4, 5, 4, 5},末尾是未指定的残留值
    for_each(a.begin(), it, [](auto it){cout << it << endl;}); // 输出 {1, 2, 3, 4, 5}
    // 有二元谓词
    vector<int> a{1, 1, 2, 3, 4, 4, 5};
    auto it = unique(a.begin(),
        a.end(), [](auto a, auto b) -> bool{return a != b;}); // lambda 作为二元谓词,每次取两个元素判断,满足条件的元素会被移除
    for_each(a.begin(), a.end(), [](auto it) -> int{cout << it << endl;}); // Error:unique 只是把"删除"的元素移到了末尾,空间还在,访问 it 到 end() 之间的区域是未定义行为
    for_each(a.begin(), it, [](auto xs){cout << xs << endl;}); // 1,1
    cout << distance(a.begin(), a.end()) << endl; // 7,distance 求出的距离还是 7
    
  2. unique_copy() 去除连续相同的值并复制到目标容器

    注意:使用此函数之前需要先为目标容器分配空间,unique_copy 并不进行内存分配,只是把结果赋值到另一个容器;如果访问未赋值部分的区域,会产生未定义行为。同样支持二元谓词,通过二元谓词判定要移除的连续值。

    vector<int> a{1, 1, 2, 3, 4, 4, 5};
    vector<int> b(7, 0);
    auto it = unique_copy(a.begin(), a.end(), b.begin());
    for_each(b.begin(), it, [](auto xs){cout << xs << endl;}); // out: 1 2 3 4 5
    
    // 二元谓词:一般不建议把二元谓词与数字直接比较,可改用 remove 实现
    auto it = unique_copy(a.begin(),
        a.end(), b.begin(), [](auto a, auto b)
        -> bool{return a == 3;});
    for_each(b.begin(), it, [](auto xs){cout << xs << endl;}); // out: 1 1 2 3,含义:a == 3 时才进行 unique_copy
    
    auto it = unique_copy(a.begin(),
        a.end(), b.begin(), [](auto a, auto b)
        -> bool{return a == b;});
    for_each(b.begin(), it, [](auto xs){cout << xs << endl;}); // out: 1 2 3 4 5,含义:a == b 时才进行复制
    

排序

  1. sort() 对容器进行排序

    sort 用于对容器元素进行排序,同样接受二元谓词。sort使用的是随机迭代器进行排序,map不支持这样使用,map返回的是双向迭代器,内部排序使用的是红黑树比较器,外部无法对map进行排序,想再次对内部元素value进行排序,只能复制出来排序

    sort(a.begin(), a.end());
    sort(a.begin(), a.end(), greater<>()); // 使用库中自带的 greater/less 仿函数
    

迭代器差值

  1. distance() 求迭代器之间的距离

    distance 用于求两个迭代器之间的距离,只用于同一容器,setmapvector 都可以使用。

    distance(c.begin(), c.end());
    

遍历容器

  1. for_each()for_each_n 遍历容器每个元素并执行函数规定的操作,第三个参数是一个函数。如果要在遍历时修改元素,set 不行,因为 set 返回的是常量迭代器。

    array<int, 5> a{1, 2, 3, 4, 5};
    for_each(a.begin(), a.end(), [](int& x){cout << x; x *= 2;}); // 元素值会被修改,因为捕获的是引用
    for_each(a.begin(), a.end(), [](auto x) -> void{x *= 2; cout << x;}); // 元素值不会被修改,捕获的是值(副本)
    for_each_n(a.begin(), 5, [](auto x) -> void{x *= 2; cout << x;}); // 第二个参数指定操作的元素个数 n
    

复制元素

  1. copy() 将容器中的元素复制到输出迭代器(out iterator)指向的位置,慎用于 mapset

    vector<vector<int>> sx(1);
    // 建议复制前先分配好空间,避免不必要的扩容
    copy(sx.begin(), sx.end(), sx.begin()); // 将 [begin, end) 之间的元素复制到从 begin() 开始的位置,会覆盖原元素
    
    // 一个有意思的做法是使用 back_inserter 进行尾插,某些支持在尾部插入的容器可以这样用,省去了手动选择插入函数的麻烦
    // set、map 等红黑树容器没有尾插方法,无法这样调用,注意
    copy(sx.begin(), sx.end(), back_inserter(sx)); // 复制时会在末尾插入元素
    
    vector<int> a{1, 2, 3};
    set<int> s;
    copy(a.begin(), a.end(), back_inserter(s)); // Error
    
    // 接下来考虑一个向 vector<vector<int>> 复制的问题
    vector<vector<int>> v(1); // 此时外部 vector 容量为 1,size 也是 1,内部有一个 vector 元素,但 v[0] 的 size 和 capacity 都是 0,是空容器
    copy(v.begin(), v.end(), back_inserter(v)); // 复制后内部空容器变为 2 个
    
  2. copy_backward() 反向复制,将容器中的元素复制到输出迭代器之前。

    mapset 类也不支持。这个算法会覆盖输出迭代器之前的元素。

    vector<int> a{1, 2, 3, 4, 5, 6};
    set<int> s;
    copy_backward(a.begin(), a.end(), s); // Error
    copy_backward(a.begin(), a.begin() + 3, a.end()); // OK,a 变为 {1, 2, 3, 1, 2, 3}
    copy_backward(a.begin(), a.begin() + 3, a.begin()); // OK,但数组不会发生任何改变,相当于复制到 begin() 之前,没有意义
    
  3. copy_n() 复制 n 个元素到目标容器,不能使用 mapset

    copy_n(a.begin(), n, b.begin()); // 底层基于 copy() 实现
    
  4. copy_if() 按条件复制,不能使用 mapset

    copy_if(a.begin(), a.end(), b.begin(), [](auto x){return x > 2;}); // 接受一元谓词,复制满足条件的元素
    

全排列

next_permutation 生成容器的下一个全排列,会直接修改原容器,也支持一个二元谓词。

next_permutation(a.begin(), a.end());

取集合

set_intersection 取交集、set_union 取并集、set_difference 取差集(单侧)、set_symmetric_difference 取对称差集(两侧)。值得注意的是,set_difference 只会输出第一个容器中的元素(即第一个容器有、第二个容器没有的元素)。这四个函数都接受一个二元谓词,且都无法使用 mapset,应用场景是 vector 等容器。

vector<int> a{1, 2, 2, 6, 7, 9};
vector<int> b{1, 2, 5, 8, 10};
vector<int> interResult;
vector<int> unionResult;
vector<int> differenceResult;
vector<int> symmetricResult;
set_intersection(a.begin(), a.end(), b.begin(), b.end(), back_inserter(interResult));
set_union(a.begin(), a.end(), b.begin(), b.end(), back_inserter(unionResult));
set_difference(a.begin(), a.end(), b.begin(), b.end(), back_inserter(differenceResult));
set_symmetric_difference(a.begin(), a.end(), b.begin(), b.end(), back_inserter(symmetricResult));

cout << "intersection:" << endl;
for_each(interResult.begin(), interResult.end(), [](auto& item){cout << item << ":";});
cout << endl;
cout << "differencesection:" << endl;
for_each(differenceResult.begin(), differenceResult.end(), [](auto& item){cout << item << ":";});
cout << endl;
cout << "unionsection:" << endl;
for_each(unionResult.begin(), unionResult.end(), [](auto& item){cout << item << ":";});
cout << endl;
cout << "symmetric_diff_section:" << endl;
for_each(symmetricResult.begin(), symmetricResult.end(), [](auto& item){cout << item << ":";});
/* output
* intersection:
* 1:2:
* differencesection:
* 2:6:7:9:
* unionsection:
* 1:2:2:5:6:7:8:9:10:
* symmetric_diff_section:
* 2:6:7:9:5:8:10 */

搜索

search 搜索子序列,search_n 搜索固定数量的连续重复值。search 接受一个二元谓词,两个函数都可用于 setmap,但 search_nset 没有意义,因为 set 中不存在重复值。

vector<int> a{1, 1, 1, 4};
vector<int> b{1, 2};
auto it = search(a.begin(), a.end(), b.begin(), b.end());
auto xs = search_n(a.begin(), a.end(), 3, 1);
int ret = it == a.end() ? -1 : 1;
int res = xs == a.end() ? -1 : 1;
cout << ret << endl; // -1,未找到
cout << res << endl; // 1,找到了 {1, 1, 1}

计数

count 计数特定值的数量,count_if 接受一个一元谓词按条件计数。两者都不可用于 mapset

vector<int> a{1, 1, 1, 4};

int num1 = count(a.begin(), a.end(), 1);
int num2 = count_if(a.begin(), a.end(),
    [](auto a){return a > 0;});
cout << num1 << endl; // 3
cout << num2 << endl; // 4

限制数值范围

clamp 用于把数值限制在指定范围内,支持任意类型的 val:如果 val 小于 lo,返回 lo;如果 vallohi 之间,返回 val;如果 val 大于 hi,返回 hi。同样也接受一个二元谓词。

int x = clamp(val, lo, hi); // 底层使用 min 和 max 实现:min(max(val, lo), hi)

填充

fillfill_n 可用于初始化容器,避免未初始化带来的未定义行为,mapset 无法使用。

void Print(const array<int, 5>& a)
{
    for_each(a.begin(), a.end(), [](auto item){cout << item << ":";});
}

int main()
{
    bool used[9];
    array<int, 5> a;
    fill(used, used + 9, false);
    fill(a.begin(), a.end(), 0);
    fill_n(a.begin(), 3, 1);
    Print(a); // {1, 1, 1, 0, 0}
}

等于

equal() 用于判断两个序列是否相等,接受一个二元谓词,可用于 mapset

map<int, int> a{{1, 2}, {3, 4}};
map<int, int> b{{1, 2}, {3, 4}};
bool flag = equal(a.begin(), a.end(), b.begin(), b.end());
if (flag == true) cout << "Yes" << endl;

二分查找

binary_search() 基于 lower_bound() 实现二分查找,返回值为 bool,支持一个二元谓词,所以把 upper_bound() 也放在一起讲解。三个函数都可用于 set,但要注意 set 不支持迭代器直接相减。upper_bound() 返回第一个大于指定值的元素的迭代器,lower_bound() 返回第一个不小于指定值的元素的迭代器。这三个函数都要求序列有序(默认升序)才能正常工作。

vector<int> a{1, 2, 4, 6, 10};
bool found = binary_search(a.begin(), a.end(), 5);
if (found) cout << "Yes" << endl;
auto it = upper_bound(a.begin(), a.end(), 5);
cout << distance(a.begin(), it) << endl; // 3,指向 6 的位置
// 以下也可以
cout << it - a.begin() << endl; // vector 等随机访问容器可直接相减

// 这样也是可以的
set<int> a{1, 2, 4, 5, 6, 10};
auto it = upper_bound(a.begin(), a.end(), 5); // it 指向 6
it = lower_bound(a.begin(), a.end(), 5); // it 指向 5
cout << distance(a.begin(), it) << endl;

移除

remove() 从序列中移除指定值的元素(remove_if 接受一元谓词按条件移除)。不可用于 mapset,因为会修改常量迭代器指向的元素。

vector<int> a{1, 2, 2, 6, 7, 9};
auto it = remove(a.begin(), a.end(), 2); // 返回值是移除后序列最后一个元素的下一个迭代器
cout << a[4] << endl; // 返回 7,因为 remove 实际上是将其后的元素复制到删除的位置,所以 map 和 set 不可用
cout << it - a.begin() << endl; // 4
cout << a.size() << endl; // 6,空间并未删除,仍然有 6 个元素,size 不变
cout << a.capacity() << endl; // 6,此时容量也是 6
posted @ 2024-08-27 23:59  LemHou  阅读(356)  评论(0)    收藏  举报