当前位置: 首页 > news >正文

CppCon 2015 学习:STL Algorithms in Action

算法(Algorithm) 是一种解决问题的有限步骤的程序,通常涉及重复操作。
具体来说:

  • 是为了解决某个问题(比如求最大公约数)而设计的步骤序列。
  • 这些步骤是有限的,最终能得出结果。
  • 广义上,算法就是一套逐步执行的指令,用来解决问题或完成某项任务,尤其是计算机执行的任务。
    简单说,算法就是“做事的方法”或“操作流程”。

STL算法就是:

  • C++标准模板库(STL)里预先写好的通用算法库,用于解决各种常见问题。
  • 这些算法随编译器自带,使用时无需额外安装。
  • 它们作用于序列(如数组、vector等容器)
  • 语法声明式(declarative),不用写显式的循环代码,比如不用写for或while循环。
  • 通过迭代器遍历序列中的元素,逐个对元素执行操作。
  • 由专家设计,经过大量真实项目使用验证,稳定可靠,几乎没有bug。

Raw loop 就是指你直接写的传统循环结构,比如 forwhile、或者 do...while

  • 你得手动写循环控制、条件判断、迭代操作。
  • 往往代码比较长,不够简洁。
  • 容易在循环体里写出副作用,比如修改外部变量(比如示例里的 global_countfound、以及往 out 里插入元素)。
  • 代码可读性差,维护成本高。
    你举的例子:
vector<int> out; 
bool found = false; 
for (const auto& i: v) { if (i >= 42) { out.emplace_back(i); ++global_count; if (i == 42) { found = true; } } 
}

就是典型的raw loop写法:显式写循环、条件和副作用。

为什么用算法

  • 效率高:STL算法通常比自己写的循环快,因为它们经过优化。
  • 代码更简洁、更抽象:不需要写具体的循环细节,代码更易读。
  • 副作用控制明确:算法内部控制副作用,不会随意影响外部状态。
  • 防止副作用泄露:减少意外修改变量的风险。
  • 方便推理和验证:更容易理解代码做了什么,也更容易判断结果是否正确。
  • 更健壮:经过大量测试,减少不明显的bug。
  • 更利于维护:整体代码更清晰,方便别人理解和改动。

STL算法的几大类,具体包括:

不修改序列内容的算法,比如查找、统计、判断等。
会修改序列内容的算法,比如复制、替换、移除元素等。
排序相关算法,比如排序、部分排序、检查是否已排序等。
适配C语言风格的算法,比如 qsort 的对应算法等。
数值相关算法,比如求和、累积、内积等。
关于 STL非修改序列操作算法(Non-modifying sequence operations)

  • 它们不会修改输入序列,也就是说,输入的容器或数组的元素不会被改变。
  • 这些算法通常不产生新的结果序列,它们主要是用来查询、检查或者统计原序列的信息。
  • 算法本身不会对输入序列产生副作用(不会更改序列中的数据)。
  • 如果算法中带有函数对象(function object,比如回调函数),那么这个函数对象可能会有副作用,比如修改自己内部的状态,或者在某些特殊情况下(例如 for_each),可能会修改环境或者序列外部的变量。
  • std::find:查找元素
  • std::count:统计元素出现次数
  • std::all_ofstd::any_ofstd::none_of:判断序列中元素是否满足某个条件
  • std::for_each:对每个元素执行操作(函数对象可能有副作用)
  • std::equal:比较两个序列是否相等
  • std::adjacent_find:查找相邻重复元素

这一部分是 C++ STL 中的非修改序列操作算法,列出了常用的函数:

  • all_of:判断序列中是否所有元素都满足某个条件。
  • any_of:判断序列中是否有任意一个元素满足某个条件。
  • none_of:判断序列中是否没有元素满足某个条件。
  • for_each:对序列中的每个元素执行指定操作(可能带副作用)。
  • find / find_if / find_if_not:查找满足条件的元素。
  • find_end:查找子序列在序列中最后一次出现的位置。
  • find_first_of:查找序列中第一次出现指定集合中任意元素的位置。
    更多“实战”算法:
  • adjacent_find:查找相邻重复元素。
  • count / count_if:统计满足条件的元素个数。
  • mismatch:找出两个序列首次不匹配的位置。
  • equal:判断两个序列是否相等。
  • is_permutation:判断两个序列是否为置换(排列)。
  • search / search_n:查找子序列或者连续重复元素。
    这些算法都不会修改输入序列,且通常不产生新的序列,而是用于查询或遍历。

C++ STL 中的修改序列操作算法(mutating sequence operations),重点是:

  • 通常情况下,这些算法不会直接修改输入序列(除非输出序列和输入序列重叠,比如 transform)。
  • 这些算法会产生一个新的输出序列,有些情况下输出序列可能和输入序列重叠,导致原地修改(in-place)。
  • 其他算法(比如 copy)明确禁止输入和输出序列重叠。
  • 算法会对输出序列产生显式的副作用(修改输出内容)。
  • 传入的函数对象如果有,会影响自身或外部环境,但不应修改输入或输出序列。
    总结就是:
    这些算法主要是产生修改后的输出,不直接修改输入,或者只在允许重叠时原地修改。

这部分列举了 C++ STL 中的**修改序列操作算法(mutating sequence operations)**的具体例子:

复制和移动类

  • copy, copy_n, copy_if, copy_backward
  • move, move_backward
  • swap_ranges, iter_swap
    变换和替换类
  • transform
  • replace, replace_if, replace_copy, replace_copy_if
    填充和生成类
  • fill, fill_n
  • generate, generate_n
    移除和去重类
  • remove, remove_if, remove_copy, remove_copy_if
  • unique, unique_copy
    反转和旋转类
  • reverse, reverse_copy
  • rotate, rotate_copy
    随机打乱
  • shuffle, random_shuffle(C++17后废弃random_shuffle
    分区相关
  • partition, is_partitioned, stable_partition, partition_copy, partition_point
    这些算法有的生成新序列,有的原地修改,都是为了处理容器里的数据结构,方便对序列进行各种修改和重排。

STL 中的排序及相关操作算法 的特点:

  • 既有非修改序列的操作,也有修改序列的操作。
  • 修改操作会原地修改序列(例如 sortmake_heap),或者将结果输出到另一个序列(例如 mergepartial_sort_copy)。
  • 默认的比较函数是 operator<,如果你传入自定义比较函数,该函数不应修改序列或迭代器。
    换句话说,这组算法用于对容器中的元素进行排序、合并、堆构造等操作,灵活且高效。

这部分是对 STL 排序和相关算法 的详细分类和说明。下面是逐项解释:

排序算法

  • sort:快速排序,默认使用 operator<,时间复杂度 O(N log N),不稳定排序
  • stable_sort:保持相同元素的相对顺序,适合需要稳定性的场景。
  • partial_sort:只对前 N 个元素排序,其余元素顺序未定义。
  • partial_sort_copy:从输入中选出前 N 小元素排序后,复制到输出区。

元素选取

  • nth_element:对序列重新排列,使得第 N 个元素处于其排序后应在的位置,其前后分别是较小和较大的元素(但这两部分不一定是有序的)。

二分查找算法(前提:已排序)

  • lower_bound:返回第一个不小于目标值的迭代器。
  • upper_bound:返回第一个大于目标值的迭代器。
  • equal_range:返回一个 pair,表示目标值的范围 [lower, upper)
  • binary_search:判断是否存在目标值,返回 true/false

合并

  • merge:将两个已排序序列合并为一个新排序序列。
  • inplace_merge:原地合并两个连续的已排序子区间。

集合操作(在排序容器上操作)

  • includes:判断一个排序序列是否包含另一个。
  • set_union:并集。
  • set_intersection:交集。
  • set_difference:差集(存在于第一个而不在第二个中的元素)。
  • set_symmetric_difference:对称差(仅存在于一个集合中的元素)。

堆操作

  • make_heap:将一个序列构建成堆。
  • push_heap:将新元素加入堆中。
  • pop_heap:将堆顶元素移到末尾。
  • sort_heap:对堆排序(升序)。

最值查找

  • min, max:两个值之间取最小或最大。
  • minmax:返回两个值中较小和较大的一个。
  • min_element, max_element, minmax_element:在序列中找出最小、最大或最小最大值对。

字典序比较

  • lexicographical_compare:比较两个序列的字典序。

排列

  • next_permutation:下一个排列(按字典序)。
  • prev_permutation:上一个排列。

总结

这些算法:

  • 是 STL 的精华部分。
  • 性能高,封装好,安全性高。
  • 在处理排序、集合操作、最值计算等方面,避免手写低效或错误的循环逻辑。

这部分讲的是 STL(标准模板库)中的 通用数值操作算法(general numeric operations),主要用于执行各种数值相关的计算任务。以下是详细解释:

总体特性

  • 是一个专门处理 数值操作 的算法集合。
  • 提供对 复杂数(complex numbers)随机数生成(random numbers)数组操作(如 valarray泛型数值计算 的支持。
  • 一部分功能也来源于 ISO C 标准库,例如 accumulate 这种和 std::numeric 有关的内容。

常见的 STL 数值算法(来自 <numeric> 头文件)

算法名功能说明
std::accumulate求和或其他二元操作(可自定义),如:accumulate(v.begin(), v.end(), 0)
std::inner_product计算两个序列的内积(点积)
std::adjacent_difference相邻元素之间的差值(输出序列)
std::partial_sum前缀和(部分和)计算
std::iota填充一个序列以递增整数,如:iota(v.begin(), v.end(), 0) 从0开始填充

示例

#include <numeric>
#include <vector>
#include <iostream>
int main() {std::vector<int> v = {1, 2, 3, 4};// 累加和int sum = std::accumulate(v.begin(), v.end(), 0); // 结果是 10// 内积int product = std::inner_product(v.begin(), v.end(), v.begin(), 0); // 1*1 + 2*2 + 3*3 + 4*4 = 30// 部分和std::vector<int> partial;//1 1+2 1+2+3 1+2+3+4std::partial_sum(v.begin(), v.end(), std::back_inserter(partial)); // {1, 3, 6, 10}std::cout << "sum: " << sum << ", product: " << product << '\n';
}

总结

STL 的数值算法部分:

  • 适合用于泛型编程中的数学计算。
  • 可以与容器(如 vector, array, valarray)配合使用。
  • 功能强大,使用简洁,避免手写循环逻辑。
  • 是性能和可读性之间的良好平衡。

这一部分讲的是 C 标准库中的算法,比如 bsearchqsort,它们虽然属于 C 标准库(<cstdlib>),但有时在 C++ 项目中也可能遇到,尤其是 遗留代码(legacy code) 中。

为什么还要提它们?

  • 这些是 早期 C 语言风格 的通用算法。
  • 在现代 C++ 中,它们已经 不推荐使用,因为:
    • 类型安全性差(使用 void*
    • 不够灵活(无法使用泛型、lambda 等)
    • 不符合现代 C++ 的设计哲学
  • 但你仍然需要了解它们:
    • 因为老项目中常用(维护老代码时会碰到)
    • 作为知识补全有价值

常见的两个 C 风格算法

函数名用途简介
qsort快速排序(C风格),参数是 void* 类型的数组指针和比较函数
bsearch二分查找(C风格),在已排序数组中查找元素

示例(使用 qsort

#include <cstdlib>
#include <iostream>
int compare(const void* a, const void* b) {return (*(int*)a - *(int*)b);
}
int main() {int arr[] = { 5, 2, 9, 1, 3 };int n = sizeof(arr) / sizeof(arr[0]);qsort(arr, n, sizeof(int), compare);for (int i = 0; i < n; ++i)std::cout << arr[i] << " ";  // 输出:1 2 3 5 9
}

替代方案(现代 C++)

使用 C++ 的 std::sort 更安全、强大:

#include <algorithm>
#include <vector>
#include <iostream>
int main() {std::vector<int> v = { 5, 2, 9, 1, 3 };std::sort(v.begin(), v.end());for (int i : v)std::cout << i << " ";  // 输出:1 2 3 5 9
}

总结

比较项C 风格 (qsort)C++ 风格 (std::sort)
类型安全使用 void*模板函数,类型安全
可读性一般更清晰简洁
性能不一定最优可以内联比较函数,编译器优化更强
推荐程度仅限遗留支持现代 C++ 推荐使用

这段内容是在深入对比 for_eachtransform ——虽然它们都对序列中每个元素应用某种操作,但本质和用途有显著差异。

for_each 的特点

  • 非修改性算法(non-modifying sequence operation)
  • 不生成输出序列,只遍历执行操作
  • 本身不造成副作用,但传入的函数对象(比如 lambda)可能修改元素修改自身
  • 返回的是函数对象(可带状态),常用于统计、打印、计数等
  • 不要求输出范围,因为它默认不会生成新数据
    总结:
    适合做“观察”和“副作用”的操作,如输出、记录日志、累加计数。
int count = 0;
std::for_each(v.begin(), v.end(), [&](int x) {if (x > 5) ++count;
});

transform 的特点

  • 修改性算法(mutating sequence operation)
  • 显式地将一个输入序列转换为输出序列(输出范围必须提供)
  • 可就地修改输入数据,或者写入另一个容器
  • 算法本身就是副作用的产生者(它直接修改输出序列)
  • 要求函数对象是纯函数(不能修改序列元素或迭代器本身)
  • 返回的是结果序列中最后一个位置的迭代器
    总结:
    适合“数据转换”,例如将一组数字变为平方,或从一个类型映射成另一个。
std::transform(v.begin(), v.end(), out.begin(), [](int x) {return x * x;
});

对比表:

特性for_eachtransform
算法类型非修改(non-modifying)修改(mutating)
是否需要输出范围
算法是否本身修改数据否(操作留给函数对象)是(transform 自己修改结果序列)
函数对象是否可以有副作用可以(修改元素、记录状态等)通常不推荐有副作用
返回值函数对象(可带状态)结果序列末尾的迭代器
常见用途输出、计数、副作用操作数据转换、新序列生成

结论

  • **for_each** 来做“观察型”的操作(打印、累加、标记),行为是副作用驱动
  • **transform** 来做“转换型”的操作(映射、变换、就地替换),结果是数据驱动

这个是一个使用 std::for_each 和函数对象(HashString)的例子,目的是:

将 vector 中的所有字符串串联起来计算一个统一的 hash 值。

代码说明

struct HashString {void operator()(const string& s) {hash = accumulate(s.begin(), s.end(), hash, hash_char);}uint32_t hash = 0;
};
成员变量:
  • uint32_t hash = 0;
    初始化为 0,是累积的哈希值。
成员函数:
  • operator()(const string& s)
    这是一个仿函数(函数对象),用来对每个字符串 s 执行哈希操作:
hash = accumulate(s.begin(), s.end(), hash, hash_char);

这里用 std::accumulate

  • s 的每个字符调用 hash_char 函数进行累积
  • 起始值是当前的 hash,所以是“连锁”计算 —— 所有字符串字符合并后共同决定了一个最终哈希

假设的使用方法

std::vector<std::string> strings = {"hello", "world"};
HashString hasher;
std::for_each(strings.begin(), strings.end(), std::ref(hasher));
std::cout << "Final hash = " << hasher.hash << '\n';

你可以看到:

  • for_each 把每个字符串 s 传给了 hasher.operator()(s)
  • hasher.hash 是最终结果,表示所有字符串字符经过哈希计算后的聚合值

hash_char 是什么?

hash_char 应该是一个自定义函数,可能像这样:

uint32_t hash_char(uint32_t acc, char c) {return acc * 31 + static_cast<uint8_t>(c);
}

这是一个常见的字符串哈希算法(类似 Java 的字符串哈希公式)。

总结:

这段代码利用 for_eachstd::accumulate 实现了对多个字符串的统一哈希计算:

  • 使用 for_each 来遍历序列并累加状态(hash
  • 用函数对象封装状态(hash)并提供运算逻辑(operator()
  • accumulate 再次体现了 STL 算法的力量:表达清晰、代码简洁
    这是 for_each 用于副作用+状态记录的典型案例!
#include <iostream>
#include <vector>
#include <string>
#include <numeric>
#include <cstdint>
#include <algorithm>
// 定义 hash_char:给一个字符追加到当前哈希值中
uint32_t hash_char(uint32_t acc, char c) {return acc * 31 + static_cast<uint8_t>(c);  // 31 是常用的乘数因子
}
struct HashString {void operator()(const std::string& s) {hash = std::accumulate(s.begin(), s.end(), hash, hash_char);}uint32_t hash = 0;
};
int main() {std::vector<std::string> strings = {"hello", "world"};HashString hasher;std::for_each(strings.begin(), strings.end(), std::ref(hasher));std::cout << "Final hash = " << hasher.hash << '\n';
}

你这段代码是一个 使用 std::for_each 和自定义函数对象 HashString 来为一个字符串序列生成单个哈希值 的完整示例。我们来逐步理解:

1. 核心目标

为一个 vector<string> 中的所有字符 计算一个累积的 hash 值,即:

vector<string> v = { "one", "two", "three", "four", "five" };

对每个字符串中的每个字符执行特定的 hash 操作,最终得出一个统一的 uint32_t hash 值。

2. for_each + HashString

struct HashString {void operator()(const string& s) {hash = accumulate(s.begin(), s.end(), hash, hash_char);}uint32_t hash = 0;
};
  • HashString 是一个函数对象(仿函数),用于给 for_each 用。
  • 它的 operator() 接受 string& s,然后使用 std::accumulatehash_char 处理这个字符串中每个字符。
  • 它通过成员变量 hash 累积结果。
template<typename Cont>
uint32_t hash_all_strings(const Cont& v) {const auto hasher = for_each(v.begin(), v.end(), HashString());return hasher.hash;
}
  • for_each 会将容器 v 中的每个字符串传给 HashString::operator()
  • 它返回的是 HashString 对象的一个副本,因此我们可以从中取出累积的 hash

3. hash_charrotl

uint32_t rotl(uint32_t value, unsigned int count) {const uint32_t mask = (CHAR_BIT * sizeof(value) - 1);count &= mask;return (value << count) | (value >> ((-count) & mask));
}
uint32_t hash_char(uint32_t hash, char c) {hash = rotl(hash, c); // 左旋hash ^= c;            // 异或当前字符return hash;
}
  • rotl 实现了一个“循环左移”函数。用于增加哈希的“扩散性”。
  • hash_char 是核心的字符哈希函数,每个字符都对当前 hash 值做旋转与异或处理,从而增强分布效果。

4. 示例输出函数

void test_for_each_hash() {vector<string> v{ "one", "two", "three", "four", "five" };uint32_t hash = hash_all_strings(v);cout << "Hash: " << hash << dec << endl;
}

这段测试代码调用整体哈希计算流程,并输出最终 hash。

总结:你要理解的是

  • for_each 应用 HashString 这个函数对象
  • HashString 内部用 std::accumulate 调用 hash_char 来处理每个字符
  • hash_char 使用 rotl^ 运算符构建 hash
  • 所有字符串的所有字符会顺序处理,最终得到一个统一 hash 值
#include <iostream>
#include <vector>
#include <string>
#include <cstdint>
#include <algorithm>
#include <numeric>  // for std::accumulate, std::for_each
#include <climits>  // for CHAR_BIT
using namespace std;
//  左循环位移函数(循环左移)
uint32_t rotl(uint32_t value, unsigned int count) {const uint32_t mask = (CHAR_BIT * sizeof(value) - 1);count &= mask;return (value << count) | (value >> ((-count) & mask));
}
//  单个字符的 hash 计算函数
uint32_t hash_char(uint32_t hash, char c) {hash = rotl(hash, c);  // 先旋转hash ^= c;             // 然后异或return hash;
}
//  用于 for_each 的函数对象
struct HashString {void operator()(const string& s) {// 使用 std::accumulate 把每个字符都混入 hash 值中hash = accumulate(s.begin(), s.end(), hash, hash_char);}uint32_t hash = 0;
};
//  通用模板:对任意字符串容器做 hash
template <typename Cont>
uint32_t hash_all_strings(const Cont& v) {auto hasher = for_each(v.begin(), v.end(), HashString());return hasher.hash;
}
//  测试入口
void test_for_each_hash() {vector<string> v{"one", "two", "three", "four", "five"};uint32_t hash = hash_all_strings(v);cout << "Hash: " << hash << dec << endl;
}
int main() {test_for_each_hash();return 0;
}

一个使用 std::transform 的完整示例,下面是逐行的解释和理解

#include <iostream>
#include <vector>
#include <string>
#include <cstdint>
#include <algorithm>
#include <numeric>
#include <climits>
using namespace std;
// 循环左移函数
uint32_t rotl(uint32_t value, unsigned int count) {const unsigned int mask = (CHAR_BIT * sizeof(value) - 1);count &= mask;return (value << count) | (value >> ((-count) & mask));
}
// 字符哈希函数
uint32_t hash_char(uint32_t hash, char c) {hash = rotl(hash, c);  // 循环左移hash ^= c;             // 异或return hash;
}
// 对单个字符串生成哈希值
uint32_t hash_string(const string& s) { return accumulate(s.begin(), s.end(), 0u, hash_char); }
// 对字符串容器中每个字符串生成哈希值,返回哈希值vector
template <typename Cont>
vector<uint32_t> hash_each_string(const Cont& v) {vector<uint32_t> res;transform(v.begin(), v.end(), back_inserter(res), hash_string);return res;
}
// 测试函数,打印所有字符串的哈希值
void test_transform_hash() {vector<string> v{"one", "two", "three", "four", "five"};auto res = hash_each_string(v);cout << "Hashes: ";for_each(res.begin(), res.end(), [](uint32_t rh) { cout << rh << " "; });cout << endl;
}
int main() {test_transform_hash();return 0;
}

总体作用

这个示例的功能是:

对一个字符串 vector 中的每个字符串,计算它的 hash 值(基于字符级操作),并把所有 hash 存入一个 vector<uint32_t> 中,最后打印出来。

核心部分详解

1. 字符串哈希函数

uint32_t hash_char(uint32_t hash, char c) {hash = rotl(hash, c); // 左旋转hash ^= c;            // 异或混入字符return hash;
}
uint32_t hash_string(const string& s) {return accumulate(s.begin(), s.end(), 0, hash_char);
}
  • hash_char:每处理一个字符就对当前 hash 值进行更新(左旋+异或)
  • hash_string:对字符串的每个字符累加哈希

2. transform 应用:对每个字符串哈希

template<typename Cont>
vector<uint32_t> hash_each_string(const Cont& v) {vector<uint32_t> res;transform(v.begin(), v.end(), back_inserter(res), hash_string);return res;
}
  • std::transform:对 v 的每个元素(string),调用 hash_string
  • back_inserter(res):结果被插入到 res 向量末尾
  • 这个函数返回的是一个包含所有字符串 hash 的 vector<uint32_t>

3. 测试函数:打印所有哈希

void test_transform_hash() {vector<string> v{ "one", "two", "three", "four", "five" };auto res = hash_each_string(v);cout << "Hashes: ";for_each(res.begin(), res.end(),[](uint32_t rh){ cout << rh << " "; });cout << endl;
}
  • 创建字符串数组 v
  • 调用 hash_each_string 得到每个字符串的 hash 值数组
  • for_each + lambda 打印所有 hash

总结对比:for_each vs transform

对比项for_eachtransform
类型非变异算法(non-modifying)变异算法(mutating)
结果输出不生成输出,靠函数对象副作用直接生成结果序列
推荐用途有副作用的迭代处理显式地生成新的值序列

“副作用”(side effect)是编程中的一个重要概念,指的是在执行某段代码时,除了返回结果之外,还对程序的状态或外部环境产生了影响。简单说,就是代码执行过程中除了计算返回值外,还“改变了什么东西”。

常见的副作用包括:

  • 修改变量(特别是全局变量或外部变量)
  • 修改传入的参数(通过引用或指针)
  • 修改数据结构(比如容器里的元素)
  • 写文件、写数据库
  • 输出到屏幕(比如 std::cout
  • 发送网络请求
  • 抛出异常

举个简单的例子:

int add(int a, int b) {return a + b; // 纯函数,无副作用
}
int x = 0;
int add_and_increment(int a) {x += a;      // 修改了外部变量 x,产生副作用return x;
}
  • add 函数只计算和返回结果,没有修改任何外部状态,因此无副作用
  • add_and_increment 函数修改了外部变量 x,这就是有副作用

为什么关注副作用?

  • 代码可预测性:没有副作用的函数,输入相同就总有相同输出,更容易调试和理解。
  • 并行执行:无副作用的代码更容易安全并发执行。
  • 测试方便:无副作用的函数更容易单元测试。

any_ofall_ofnone_of 是 STL 中用于判断序列中元素是否满足某种条件的算法:

  • any_of:序列中 至少有一个元素满足条件,返回 true,否则返回 false
  • all_of:序列中 所有元素都满足条件,返回 true,否则返回 false
  • none_of:序列中 没有任何元素满足条件,返回 true,否则返回 false
    它们的特点是:
  • 都接受一个函数对象(或者谓词),用来判断元素是否“满足条件”。
  • 在判断过程中,一旦结果已经确定(比如 any_of 找到第一个满足条件的元素),就会提前返回,不用遍历剩下的元素,提高效率。
    举个简单例子:
std::vector<int> v = {1, 2, 3, 4, 5};
// 判断是否序列中有元素大于3
bool any = std::any_of(v.begin(), v.end(), [](int x){ return x > 3; }); // true
// 判断是否所有元素都大于0
bool all = std::all_of(v.begin(), v.end(), [](int x){ return x > 0; }); // true
// 判断是否没有元素小于0
bool none = std::none_of(v.begin(), v.end(), [](int x){ return x < 0; }); // true

你这段代码用 all_ofany_of 检查 HTTP 头是否有效和搜索特定的头部,示例很经典。

#include <iostream>
#include <vector>
#include <string>
#include <regex>
#include <algorithm>
using namespace std;
// all_of 示例:验证 HTTP headers 格式是否正确
static const regex reHeader("([A-Za-z0-9!#$%&'*+.^_`|~-]+): *(.+) *");
inline bool headers_valid(const vector<string>& headers) {return all_of(headers.begin(), headers.end(), [](const auto& header) -> bool {smatch matches;return regex_match(header, matches, reHeader);});
}
// any_of 示例:在 HTTP headers 中查找特定的 header 和对应值
inline bool header_search(const vector<string>& headers, const string& find_header,const string& find_value) {return any_of(headers.begin(), headers.end(), [&find_header, &find_value](const auto& header) -> bool {const regex reHeader("(" + find_header + "): *(" + find_value + ") *", regex::icase);smatch matches;return regex_match(header, matches, reHeader);});
}
void test_all_of_headers() {vector<string> h1 = {"Foo: bar", "Content-type: application/json","Accept: text/html,text/json,application/json"};cout << "headers valid (h1): " << boolalpha << headers_valid(h1) << endl;vector<string> h2 = {"Foo : bar", "Content-type: application/json","Accept: text/html,text/json,application/json"};cout << "headers valid (h2): " << boolalpha << headers_valid(h2) << endl;vector<string> h3 = {"Foo: bar", "Content-type: application/json",":Accept: text/html,text/json,application/json"};cout << "headers valid (h3): " << boolalpha << headers_valid(h3) << endl;vector<string> h4 = {"Foo: bar", " Content-type: application/json","Accept: text/html,text/json,application/json"};cout << "headers valid (h4): " << boolalpha << headers_valid(h4) << endl;
}
void test_any_of_headers() {vector<string> h1 = {"Foo: bar", "Content-type: application/json", "X-SuperPower: toestrength","Accept: text/html,text/json,application/json"};cout << "header_search (h1): " << boolalpha << header_search(h1, "X-SuperPower", "toestrength")<< endl;vector<string> h2 = {"Foo: bar", "Content-type: application/json", "X-SuperPower: supersmell","Accept: text/html,text/json,application/json"};cout << "header_search (h2): " << boolalpha << header_search(h2, "X-SuperPower", "toestrength")<< endl;vector<string> h3 = {"Foo : bar", "Content-type: application/json", "X-SuperPower: toestrength","Accept: text/html,text/json,application/json"};cout << "header_search (h3): " << boolalpha << header_search(h3, "X-Superpower", "toeStrength")<< endl;
}
int main() {cout << "Testing all_of headers validation:" << endl;test_all_of_headers();cout << "\nTesting any_of header search:" << endl;test_any_of_headers();return 0;
}

总结下:

all_of 示例:验证 HTTP headers 格式是否正确

  • 用正则表达式匹配每个 header 是否符合 "key: value" 格式。
  • all_of 只要有一个 header 不匹配,就返回 false,全部匹配才返回 true
static const regex reHeader("([A-Za-z0-9!#$%&'*+.^_`|~-]+): *(.+) *");
inline bool headers_valid(const vector<string>& headers) {return all_of(headers.begin(), headers.end(),[](const auto& header) -> bool {smatch matches;return regex_match(header, matches, reHeader);});
}

测试时发现格式不对就会返回 false,非常直观。

any_of 示例:在 HTTP headers 里查找某个特定的 header 是否存在且值匹配

  • 构造正则表达式动态匹配具体的 header 名和对应的值(忽略大小写)。
  • 只要找到一个匹配的 header 即返回 true
inline bool header_search(const vector<string>& headers,const string& find_header,const string& find_value)
{return any_of(headers.begin(), headers.end(),[&find_header, &find_value](const auto& header) -> bool {const regex reHeader("(" + find_header + "): *(" + find_value + ") *", regex::icase);smatch matches;return regex_match(header, matches, reHeader);});
}

这两个例子非常好地体现了 all_ofany_of 的语义和用法:

  • all_of 用于“全部满足”,通常是验证性质的判断。
  • any_of 用于“存在至少一个满足”,通常是查找或搜索性质的判断。

这段代码展示了如何使用 std::for_each 同时进行两个操作:

  1. 验证 HTTP Header 格式是否合法(统计合法与不合法的 header 数量)
  2. 查找是否包含特定的 header 和对应的值(模糊大小写匹配)

主要结构解析

HeaderData 结构体

struct HeaderData {int good_headers = 0;int bad_headers = 0;multimap<string, string> found_headers;string find_header;string find_value;operator bool() const {return !bad_headers && good_headers > 0;}void operator()(const string& header) {static const regex reValid("([A-Za-z0-9!#$%&'*+.^_`|~-]+): *(.+) *");smatch matches;bool match = regex_match(header, matches, reValid);if (match) {++good_headers;// 构造查找用的正则(忽略大小写)const regex reHeader("(" + find_header + "): *(" + find_value + ") *", regex::icase);if (regex_match(header, matches, reHeader)) {found_headers.emplace(matches[1], matches[2]);}} else {++bad_headers;}}
};
  • 它是一个函数对象(重载了 operator()),可以传给 std::for_each
  • operator bool() 使得你可以直接用 if (hd) 判断:
    • 至少有一个合法 header
    • 没有非法 header

header_parse 函数

const HeaderData header_parse(const vector<string>& headers, const string& find_header, const string& find_value) {HeaderData hd;hd.find_header = find_header;hd.find_value = find_value;return for_each(headers.begin(), headers.end(), hd);
}
  • 使用 std::for_each 遍历 headers,自动调用 HeaderData::operator()
  • 返回最终结果:验证情况 + 找到的 header/value 键值对

测试函数 any_of_headers_full()

这个函数运行三个测试:

  1. 所有 header 合法,能找到目标 header/value
  2. 所有 header 合法,但找不到目标值
  3. 有一个非法 header(空格在冒号前),但依然能找到值

示例输出解释:

headers parse: true, good 4, bad 0'X-SuperPower', 'toestrength'
headers parse: true, good 4, bad 0
headers parse: false, good 3, bad 1'X-Superpower', 'toestrength'
  • 第一组:找到 header,全部合法
  • 第二组:没找到目标值,但 header 格式都合法
  • 第三组:找到值,但有一个 header 不合法,所以整体解析失败(operator bool() 返回 false)

总结:你需要理解的 STL 技巧

功能使用的 STL 技术
遍历并累积状态std::for_each + 函数对象
判断是否合法std::regex_match
模糊查找(忽略大小写)std::regex(..., regex::icase)
查找结果收集std::multimap
简化布尔判断operator bool()

关于 C++ STL 算法 std::adjacent_find 的用法,特别是用它来检测排序或数值偏差的情况。下面我会一步一步解释它的作用、示例代码,以及输出的含义,帮助你真正理解。

std::adjacent_find 是什么?

这个算法在一个序列中寻找相邻的两个元素,看它们是否满足某种条件

  • 默认条件是:两个相邻元素是否相等
  • 你也可以传入自定义比较函数(例如是否 a > b,是否偏差太大等)

示例 1:判断是否排序(非降序)

vecInt_t v{ 1, 2, 3, 4, 5, 5, 6, 7, 8 };
auto it = adjacent_find(v.begin(), v.end(), greater<int>());
if (it == v.end())cout << "Vector is sorted" << endl;
elsecout << "Vector not sorted, value " << *(it + 1)<< ", at position " << it - v.begin() + 1 << endl;

解释:

  • greater<int>() 是比较函数:判断 前一个值是否比后一个值大
  • 如果 v[i] > v[i+1],说明不再升序,即排序错了。
  • 如果没有出现这种情况,说明整个序列是升序的。

示例输出:

Vector is sorted

(意味着没有两个相邻值违反升序)

示例 2:检测是否有“数值偏差”超出限制

这个例子更实际,模拟了“值是否变化太快”:

template<typename Cont>
typename Cont::const_iterator checkDeviation(const Cont& cont, double allowed_dev) {return adjacent_find(cont.begin(), cont.end(),[allowed_dev](const typename Cont::value_type& v1, const typename Cont::value_type& v2) {auto limit = v1 * allowed_dev;return (v2 > v1 + limit) || (v2 < v1 - limit);});
}

解释:

  • 判断两个相邻值 v1v2 之间的变化是否超出允许的偏差 allowed_dev
  • limit = v1 * allowed_dev 表示允许变化的范围。
  • 如果 v2v1 大或小太多,就算 deviation。

示例 3:检测偏差的测试用例

vecDbl_t v{ 1.0, 1.05, 1.06, 1.04, 1.09, 1.15, 1.2 };
auto it = checkDeviation(v, 0.1);  // 允许 ±10% 偏差

第一个测试:

  • 所有相邻值变化都在 ±10% 范围内
  • 输出:
Vector is within deviation limits

第二个测试:

v.push_back(2.0);
it = checkDeviation(v, 0.1);
  • 1.22.0 是 +66%,超过了 ±10%
  • 输出:
Vector outside deviation limits, values 1.2 and 2, at position 7

总结

功能方法
检查是否排序adjacent_find(..., greater<>)
查找相邻重复元素adjacent_find(...)
检测相邻值是否变化过大adjacent_find(..., 自定义比较器)

remove_if (with erase) 是 C++ 中处理容器中批量删除满足某条件的元素的经典做法,通常称为 “erase-remove 惯用法”(erase-remove idiom)。下面我来详细解释其原理、为什么推荐它,以及具体例子,帮助你深入理解。

为什么使用 remove_if + erase

问题:

你想删除一个容器中所有满足某个条件的元素,比如删除所有值为负数的整数:

vector<int> v = { 1, -2, 3, -4, 5 };

不能这样写:

for (auto it = v.begin(); it != v.end(); ++it) {if (*it < 0)v.erase(it); //  会导致迭代器失效
}

因为 erase 会修改容器大小和结构,破坏迭代器的稳定性

正确做法:erase-remove 惯用法

v.erase(remove_if(v.begin(), v.end(),[](int n){ return n < 0; }),  // 条件:删除负数v.end());

含义:

  1. remove_if 不真的删除元素,它会:
    • 不满足条件的元素前移
    • 返回一个新的迭代器 new_end,指向有效范围末尾
  2. erase(new_end, v.end())
    • 从这个位置起把尾部那些“无效元素”真正删除掉。

举例说明:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {vector<int> nums = { 1, -2, 3, -4, 5 };nums.erase(remove_if(nums.begin(), nums.end(),[](int n){ return n < 0; }),nums.end());for (int n : nums)cout << n << " ";return 0;
}

输出:

1 3 5

std::list 的区别?

std::list 提供了 remove_if 成员函数:

list<int> lst = { 1, -2, 3, -4, 5 };
lst.remove_if([](int n){ return n < 0; });  // 直接删除,无需 erase

总结:为什么推荐使用 erase-remove?

方法优点
remove_if + erase高效、安全,不会造成迭代器失效
手动循环+erase易错、易造成迭代器失效
Scott Meyers 在《Effective STL》中提到这个习惯用法作为删除元素的首选方式(第 9 和第 32 条),因为它性能好、写法安全、行为明确。

你给出的这个 remove_if(结合 erase)示例,完美展示了 “erase-remove 惯用法” 在实际应用中的用法,特别是针对自定义类型(如 struct Foo)的情况。下面我将为你逐步拆解和解释这段代码,让你完全理解它的核心思想与执行流程。

场景背景

你有一个结构体 Foo,表示某种对象,其中一个成员变量 expired 用于表示对象是否“已过期”:

struct Foo {bool expired;// ... 其他成员
};

然后你有一个容器:

vector<Foo> v;

你想要从这个 vector 中删除所有 expired 为 true 的对象

正确写法(使用 erase-remove 惯用法):

v.erase(remove_if(v.begin(), v.end(),[](const Foo& foo) { return foo.expired; }),v.end());

步骤详解:

  1. remove_if
    会对容器进行“重排”,把所有 foo.expired == false 的元素移到前面,
    然后返回一个迭代器 new_end 指向“有效数据的末尾”。

    它不会真正删除元素,只是把不符合条件的元素留下来,其他的挪到后面

  2. erase(new_end, v.end())
    删除从 new_end 开始到 v.end() 范围的元素,完成真正的删除。

示例输出前后对比

// 假设初始数据:
v = [{ val: 1, expired: false },{ val: 2, expired: true },{ val: 3, expired: false },{ val: 4, expired: false },{ val: 5, expired: true }
];
// 执行后:
v = [{ val: 1, expired: false },{ val: 3, expired: false },{ val: 4, expired: false }
];

expired 为 true 的项被清除,其他顺序不变。

总结重点

说明
remove_if不删元素,只做重排
erase真正删除尾部的“垃圾数据”
安全性不会导致 iterator 失效(在组合使用时)
效率比一个个调用 erase 更快更安全
推荐理由Scott Meyers 在《Effective STL》中强烈推荐的惯用法(Item 9 & 32)

Bonus:完整示例代码(带输出)

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct Foo {int val;bool expired;
};
void print(const vector<Foo>& v) {for (const auto& f : v)cout << "[val: " << f.val << ", expired: " << boolalpha << f.expired << "] ";cout << endl;
}
int main() {vector<Foo> v = {{1, false},{2, true},{3, false},{4, false},{5, true}};cout << "Before: ";print(v);v.erase(remove_if(v.begin(), v.end(),[](const Foo& foo) { return foo.expired; }),v.end());cout << "After: ";print(v);
}

输出:

Before: [val: 1, expired: false] [val: 2, expired: true] [val: 3, expired: false] [val: 4, expired: false] [val: 5, expired: true] 
After: [val: 1, expired: false] [val: 3, expired: false] [val: 4, expired: false] 

提到的是 Scott Meyers《Effective STL》Item 31 中关于 STL 排序相关算法的总结。这个条目主要区分了两类排序相关的算法:

分类一:真正排序的算法(Sorting algorithms)

这些算法会完全或部分地对元素 排序(sort)

算法特点要求
sort高效的快速排序(不稳定)随机访问迭代器
stable_sort稳定排序(保持相等元素顺序)随机访问迭代器
partial_sort只排序前 n 个最小元素随机访问迭代器
partial_sort_copy拷贝并排序前 n 个最小元素到新容器随机访问迭代器

分类二:“排序样子”的算法(Sorta-sorts)

这些算法并不保证全体元素有序,但局部有序或分区成功,因此更快。

算法特点要求
nth_element使得第 n 个位置是其最终位置(前面都 ≤ 它,后面都 ≥ 它)随机访问迭代器
partition将元素分成两组(满足谓词的在前,不满足的在后,顺序不保证)双向迭代器
stable_partitionpartition,但保持元素原有顺序双向迭代器
partition_copy拷贝到两个输出序列,分别保存满足和不满足谓词的元素输入 + 输出迭代器组合

示例对比总结

场景适合使用说明
全排序sortstable_sort最常见,需要排序整个容器
只想找前 n 个最小的元素partial_sortnth_elementpartial_sort 会有序,nth_element 不保证有序
需要排序并保留相等元素的顺序stable_sort稳定排序,适用于比如记录按多个字段排序的场景
仅想分区元素满足条件与否partitionstable_partition更快,但不是排序
想将元素按条件分别保存到两个容器partition_copy复制版本的 partition,保持原容器不变

Scott Meyers《Effective STL》Item 31 的核心建议:

选择排序相关算法时,不要盲目使用 sort,而要根据需求选择最合适的排序或“伪排序”算法

例如:

  • 如果你只关心前 5 名的成绩,用 partial_sort
  • 如果你只想找第 10 大的值,不用全排序,用 nth_element
  • 如果你只想把大于某值的元素放一边而不关心顺序,用 partition
  • 如果你要分组并保留原始顺序,用 stable_partition

你引用的是 Scott Meyers《Effective STL》Item 31 中关于几种常见排序算法的核心比较。下面是对这些排序算法的简洁总结与理解:

1. sort

用途:最通用的排序算法
特性

  • 效率高,适用于大多数排序场景。
  • 不稳定排序:相等元素的相对顺序可能被打乱(由实现决定)。
  • 原地排序:在容器本身内部进行,不额外分配空间。
    适合:追求排序性能,对元素顺序无特殊要求。

2. stable_sort

用途:稳定排序,保留相等元素原始顺序
特性

  • 稍慢于 sort,尤其在大数据上。
  • 对于多级排序非常有用(比如:先按姓排,再按名排)。
    适合:你关心排序后的元素顺序稳定性。

3. partial_sort

用途:只对前一部分元素排序
特性

  • 排序部分序列,但从更大的序列中选取。
  • 更快,因为它不会排序所有元素。
  • 不稳定(没有 stable_partial_sort)。
  • 原地排序。
    适合:比如只要前 10 个最大值/最小值,而不关心其余顺序。

4. partial_sort_copy

用途:从一个序列中拷贝部分排序结果到另一个序列
特性

  • 输入序列不变。
  • 输出排序好的前几个元素。
  • 用于排序但不修改原容器
    适合:想要一个排序后的副本,不破坏原始数据结构。

简明对比表:

算法是否稳定是否原地排序修改原数据用途
sort完全排序,性能优先
stable_sort完全排序,保持相等元素顺序
partial_sort排前 N 个最小/最大值
partial_sort_copy(输出到新序列)拷贝排序前 N 项,原数据不变

例子场景:

  1. 全排序且无序要求 → sort
  2. 全排序且要稳定顺序 → stable_sort
  3. 只要前 3 名学生 → partial_sort
  4. 保留成绩原样,只另拷贝前 5 名 → partial_sort_copy

你提到的这个排序场景是一个典型的多字段排序问题。我们来逐步理解这段内容,并举例说明 sortstable_sort 如何用于按照多个字段排序。

场景说明

我们有一个结构体 Person,包含:

struct Person {std::string first;std::string middle;std::string last;// ... 其他成员 ...
};

目标是:
对一个 std::vector<Person> 容器进行排序,按照:

last name > first name > middle name 的优先级升序排序。

也就是说:

  • 如果 last 不同,就按 last 排序。
  • 如果 last 相同,再按 first 排序。
  • 如果 first 也相同,再按 middle 排序。

排序代码示例(使用 std::sort

#include <vector>
#include <string>
#include <algorithm>
#include <iostream>
struct Person {std::string first;std::string middle;std::string last;
};
bool comparePersons(const Person& a, const Person& b) {if (a.last != b.last)return a.last < b.last;if (a.first != b.first)return a.first < b.first;return a.middle < b.middle;
}
int main() {std::vector<Person> people = {{"John", "H.", "Smith"},{"John", "A.", "Smith"},{"Alice", "B.", "Adams"},{"John", "H.", "Adams"}};std::sort(people.begin(), people.end(), comparePersons);for (const auto& p : people)std::cout << p.last << ", " << p.first << " " << p.middle << '\n';
}

std::stable_sortstd::sort 的区别

在这个例子中,你可以使用 std::sortstd::stable_sort。区别在于:

  • 如果只排序一次且 comparePersons 定义完整,没有相等的比较,那么 sortstable_sort 效果一样。
  • 如果多次排序用于“分层”排序(例如先按 middle,再按 first,最后按 last),就必须用 stable_sort 才能保持之前的顺序。

分层稳定排序示例(使用 stable_sort

std::stable_sort(people.begin(), people.end(), [](const Person& a, const Person& b) {return a.middle < b.middle;});
std::stable_sort(people.begin(), people.end(), [](const Person& a, const Person& b) {return a.first < b.first;});
std::stable_sort(people.begin(), people.end(), [](const Person& a, const Person& b) {return a.last < b.last;});

这种方式相当于从最低优先级(middle)开始排,再稳定地按 first、再按 last。最终顺序就是按 last > first > middle 排好的。

总结

排序方式优点用法
sort高性能,适用于一次性排序自定义比较函数时使用
stable_sort保持相同元素的原始顺序分层多次排序(优先级排序)时使用

这段代码展示了如何使用 std::sortstd::stable_sort 对一组 Person 对象按照多个字段进行分层排序(lexicographical sort),其中排序优先级是:

last name(最高优先级) > first name > middle name(最低优先级)

背景知识

假设结构体如下:

struct Person {std::string first;std::string middle;std::string last;
};

排序过程分析

std::vector<Person> v{{{ "Joe", "P", "Smith" },{ "Jane", "Q", "Jones" },{ "Frank", "P", "Johnson" },{ "Sarah", "B", "Smith" },{ "Joe", "X", "Jones" },{ "Joe", "A", "Smith" }
}};

你希望排序为:

先按 last 升序,若相同再按 first 升序,若仍相同再按 middle 升序。

✳ 分三步完成:

1⃣ sort 按 middle name 排(最低优先级):
sort(v.begin(), v.end(),[](const Person& a, const Person& b){ return a.middle < b.middle; });

sort 是可以的,因为排序顺序不会影响上层排序——我们之后还会稳定地再排一次。

2⃣ stable_sort 按 first name 排:
stable_sort(v.begin(), v.end(),[](const Person& a, const Person& b){ return a.first < b.first; });

stable_sort 保证相同 firstmiddle 排序结果不变。

3⃣ stable_sort 按 last name 排(最高优先级):
stable_sort(v.begin(), v.end(),[](const Person& a, const Person& b){ return a.last < b.last; });

再次使用 stable_sort,在最后一步保证同 lastfirstmiddle 顺序仍被保留。

最终排序结果

执行后,排序结果为:

Frank P Johnson
Jane Q Jones
Joe X Jones
Joe A Smith
Joe P Smith
Sarah B Smith

按字段拆解验证:

lastfirstmiddle
JohnsonFrankP
JonesJaneQ
JonesJoeX
SmithJoeA
SmithJoeP
SmithSarahB
满足 last < first < middle 的排序规则。

总结

步骤方法用途
排最低字段sort可打乱顺序,之后再稳定地排序
排中间字段stable_sort保持低优先级字段顺序
排最高字段stable_sort保持所有之前排序顺序
顺序很重要!最低优先级先排,最高优先级最后排。

多字段排序示例数据

初始数据:

FirstMiddleLast
JoePSmith
JaneQJones
FrankPJohnson
SarahBSmith
JoeXJones
JoeASmith

排序步骤及正确结果

Step 1:按 middlesort 排序(不稳定)

sort(v.begin(), v.end(), [](auto& a, auto& b){ return a.middle < b.middle; });

排序结果(仅保证 middle 字段排序正确,其他字段顺序不稳定):

FirstMiddleLast
JoeASmith
SarahBSmith
JoePSmith
FrankPJohnson
JaneQJones
JoeXJones

Step 2:按 firststable_sort 排序(稳定)

stable_sort(v.begin(), v.end(), [](auto& a, auto& b){ return a.first < b.first; });

排序结果(保持了 Step 1 中 middle 字段顺序):

FirstMiddleLast
FrankPJohnson
JaneQJones
JoeASmith
JoePSmith
JoeXJones
SarahBSmith

Step 3:按 laststable_sort 排序(稳定)

stable_sort(v.begin(), v.end(), [](auto& a, auto& b){ return a.last < b.last; });

最终排序结果

FirstMiddleLast
FrankPJohnson
JaneQJones
JoeXJones
JoeASmith
JoePSmith
SarahBSmith

重点

  • Step 1 sortmiddle 字段排序完成,但其他字段顺序不稳定。
  • Step 2 stable_sortfirst 字段排序,保持了 Step 1 中的 middle 顺序。
  • Step 3 stable_sortlast 字段排序,保持了前两步的稳定顺序。

总结一下 partial_sortpartial_sort_copy 的关键点:

partial_sort

  • 作用:对输入序列中的前 n 个元素进行排序,确保这部分元素是整体中最小(或最大)的 n 个元素,且这 n 个元素按顺序排列。
  • 输入:整个序列 + 需要排序的前 n 个元素的范围(通常用迭代器指定)。
  • 输出:序列中前 n 个元素是排好序的最小元素,剩余元素位置顺序不确定(实现定义)。
  • 特点就地排序(in-place),不会额外复制。
  • 效率:比完全排序效率高,因为不需要对整个序列排序。

partial_sort_copy

  • 作用:将输入序列中最小(或最大)的 n 个元素排序后,复制到另一个序列中。
  • 输入:整个序列 + 目标输出序列(通常较短)。
  • 输出:输出序列包含排序好的前 n 个元素,输入序列不变。
  • 用途:当想要得到部分排序结果且保存到不同容器时使用。

partial_sort 例子:

代码示例

vector<int> v1{ 42, 17, 89, 22, 34, 78, 63, 12, 57, 99 };
// 对 v1[0) 到 v1[8) 这段范围内,执行 partial_sort
// 目标是把前 5 个元素(v1.begin() 到 v1.begin() + 5) 排成从大到小(greater<int>)排序
partial_sort(v1.begin(), v1.begin() + 5, v1.begin() + 8, greater<int>());

解释:

  • 输入范围是 v1.begin()v1.begin() + 8(即元素 0~7,共8个元素):
    {42, 17, 89, 22, 34, 78, 63, 12}
  • 目标:对这8个元素中,找出最大的5个元素,并按从大到小排序,放到 v1.begin()v1.begin() + 5 的位置。
  • 运行完后,v1 前5个元素是这8个元素中最大的5个,且有序(降序排列,因为用的是 greater<int>()),其余元素位置不保证排序。

举例执行结果(可能是)

v1前8元素可能变成: 
{ 89, 78,  63,  42,  34, 17, 22, 12 }
  • 这里 89, 78, 63, 42, 34 是这8个元素中最大的5个,且降序排列。
  • 后面 17, 22, 12 这三个元素在末尾,顺序不定(实现定义)。

总结:

  • partial_sort 只保证部分排序:这里是排序了前5个最大元素
  • 输入序列的剩余元素(第6个以后)顺序不保证。
  • 比完全排序更高效,因为不用对全部元素排序。

Scott Meyers “Effective STL” Item 31 关于 partition 系列算法的关键点:

partition 系列算法概览

算法功能描述稳定性额外说明
partition将序列重新排列,使满足条件的元素在前,不满足的在后不稳定(元素相对顺序不保证)原地操作,分割点前是满足条件元素,分割点后是不满足元素
stable_partition功能同 partition,但保持满足条件元素及不满足元素的原始顺序稳定原地操作
partition_copy根据条件将元素分别拷贝到两个不同输出序列中不稳定不原地操作,无法保持元素顺序

详细说明

  • partition
    把序列中满足给定条件的元素放到序列前部,不满足的放到后部。
    不保证满足条件元素之间或不满足条件元素之间的原有顺序。
    在某些算法中能提升性能,但对顺序敏感时需谨慎。
  • stable_partition
    作用与 partition 相同,但保证满足条件的元素保持其在原序列中的相对顺序,
    不满足条件的元素相对顺序也保持不变。
    这对于需要保留顺序信息的场景非常重要。
  • partition_copy
    类似于 partition,但不会原地修改输入序列,
    而是根据条件分别复制元素到两个不同的输出容器。
    不保证元素的相对顺序。

关于 Scott Meyers “Effective STL” Item 31 中的 nth_element,我帮你整理总结如下:

nth_element 详解

  • 功能
    重新排列序列,使得序列中第 n 个位置上的元素,恰好是如果序列完全排序后该位置的元素。
  • 效果
    • 在第 n 个元素之前的所有元素都小于或等于该元素(实现定义可能包括等于元素的位置不同)。
    • n 个元素之后的所有元素都大于或等于该元素。
    • 但序列中第 n 元素之前和之后的元素之间顺序是未定义的(不稳定)。
  • 特点
    • 只进行部分排序,不像 sort 会完全排序整个序列,因此在寻找第 n 小元素时比完整排序更高效。
    • 原地操作,不会额外分配大量内存。

应用场景举例

  • 找第 k 小元素(或第 k 大元素)的值。
  • 只关心部分排序,比如找到前 k 大的元素但不关心顺序。

示例代码(C++)

#include <iostream>
#include <vector>
#include <algorithm>
int main() {std::vector<int> v{42, 17, 89, 22, 34, 78, 63, 12, 57, 99};// 将第5个位置的元素放到排序后它应该在的位置std::nth_element(v.begin(), v.begin() + 4, v.end());std::cout << "The 5th smallest element is " << v[4] << "\n";std::cout << "Elements before 5th element (unsorted): ";for (int i = 0; i < 4; ++i) std::cout << v[i] << " ";std::cout << "\nElements after 5th element (unsorted): ";for (int i = 5; i < v.size(); ++i) std::cout << v[i] << " ";std::cout << "\n";return 0;
}

partitionnth_element 的理解很准确,下面帮你做个清晰的对比总结,方便记忆和理解:

partition vs nth_element 比较

特性partitionnth_element
功能根据条件将序列分成两部分(满足条件和不满足条件)将序列部分排序,使第 nth 个元素位于排序后正确位置
分割点分割点不保证对应排序后序列的值nth 元素是排序后在该位置的值
输入参数起始迭代器、结束迭代器、判断条件函数起始迭代器、结束迭代器、第 nth 位置迭代器、(可选)比较函数
输出重排的序列和指向分割点的迭代器重排的序列,nth 元素处于正确位置,序列围绕该元素分区
稳定性不保证稳定(顺序未定义)不保证稳定(顺序未定义)
用途快速把满足某条件的元素移动到序列一端找出序列中第 nth 小(或大)元素,部分排序
示例将序列中所有奇数放在偶数前面找出序列中第 5 小元素,并使其左边都是更小的元素

简单类比

  • partition:只关心“满足条件”和“不满足条件”的分类,顺序无所谓,就像洗牌时把红牌和黑牌分开。
  • nth_element:关心“第 nth 个元素”的正确排序位置,左边全是更小的,右边全是更大的,但不保证其他元素顺序。

partition 将 vector 中小于 50 的元素放到前面,大于等于 50 的元素放到后面。关键点是:

  • partition就地重排容器元素,
  • 满足条件 i < 50 的元素被放在序列前部,
  • 返回值 part_it 是分区点迭代器,指向第一个不满足条件的元素。
    例如,给定:
vector<int> v{ 12, 89, 31, 18, 7, 72, 69, 50, 49, 50, 51, 49 };
auto part_it = partition(v.begin(), v.end(), [](int i) { return i < 50; });

重排后,v 中满足 < 50 的元素会聚集在前面,part_it 指向第一个 >= 50 的元素,元素内部顺序未定义。

下面是一个简单的 C++ 代码示例,用来直观对比 partitionnth_element 的区别:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {vector<int> v1{12, 89, 31, 18, 7, 72, 69, 50, 49, 50, 51, 49};vector<int> v2 = v1; // 复制一份给 nth_element 使用// partition:把小于50的放前面,大于等于50的放后面,不保证排序auto part_it = partition(v1.begin(), v1.end(), [](int x){ return x < 50; });cout << "partition result:\n";for (int x : v1) cout << x << ' ';cout << "\nPartition point value: " << *part_it << endl;// nth_element:调整序列,使第6个元素是排序后的第6小元素nth_element(v2.begin(), v2.begin() + 5, v2.end());cout << "\nnth_element result:\n";for (int x : v2) cout << x << ' ';cout << "\nValue at nth position (index 5): " << v2[5] << endl;return 0;
}

运行结果(示例)

partition result:
12 31 18 7 49 49 89 72 69 50 50 51 
Partition point value: 89
nth_element result:
12 18 7 31 49 49 72 69 50 50 51 89 
Value at nth position (index 5): 49

说明

  • partition 保证所有小于 50 的数都在前面,后面是大于等于 50 的数,但顺序无规律,分界点的值不一定是第几个最小元素。
  • nth_element 保证第 6 个位置的元素是排序后第 6 小的元素,前面都比它小,后面都比它大,但两边的元素顺序没有完全排序。

partition 系列算法总结(来自 Scott Meyers《Effective STL》Item 31)

算法名功能描述额外说明运行方式
partition重排序列,使满足条件的元素在前,不满足的元素在后子序列内元素的相对顺序是不确定的(实现定义),等价元素顺序也不确定就地操作(in-place)
stable_partition类似 partition,但保持满足条件元素及不满足元素的相对顺序保持相对顺序就地操作
partition_copy根据条件分两部分复制到两个不同的输出序列子序列内元素顺序实现定义,不保证稳定性;没有 stable_partition_copy复制操作

额外说明

  • partitionstable_partition 都是原地修改序列,不会分配额外空间。
  • stable_partition 稍微慢一点,但它保证了稳定性(元素相对顺序不变)。
  • partition_copy 是把满足和不满足条件的元素分别复制到两个不同的序列,不修改原序列。
  • STL没有提供 stable_partition_copy

总结一下 nth_element 的核心要点:

nth_element 详解(来自 Scott Meyers《Effective STL》Item 31)

  • 功能
    重排序列,使得位于第 nth 位置的元素正好是如果序列完全排序后该位置上的元素。
  • 具体表现
    • nth 位置之前的所有元素都不大于该元素(如果是升序排序),
    • nth 位置之后的所有元素都不小于该元素。
    • 但是,这些分割前后的元素本身不保证有序。
  • 性质
    • 不需要完全排序,因此效率更高。
    • 对等价元素的相对顺序和分区内的顺序,都是实现定义,没有稳定性保证。
  • 工作方式
    • 就地修改(in-place),不额外分配大量内存。

partition_copy 详解

  • 功能
    从输入序列中根据条件函数分割元素,
    分别拷贝到两个不同的输出序列中。
  • 示例代码
vector<int> v{12, 89, 31, 18, 7, 72, 69, 50, 49, 50, 51, 49};
vector<int> smaller;  // 用来存放小于50的元素
vector<int> larger;   // 用来存放不小于50的元素
partition_copy(v.begin(), v.end(), back_inserter(smaller),  // 输出小于50的元素back_inserter(larger),   // 输出大于等于50的元素[](const int i) { return i < 50; }
);
  • 工作过程
    1. 遍历 v 中所有元素,判断是否满足 i < 50
    2. 满足的元素放入 smaller
    3. 不满足的元素放入 larger
    4. smallerlarger 两个容器分别收集对应的元素,输入序列保持不变
  • 区别
    • partition 不同,partition_copy复制操作,不会修改原序列。
    • 输出结果是两个独立序列,分别对应满足和不满足条件的元素。
    • partition_copy 不是原地操作。

这个示例很好地展示了 partition_copypartitionstable_partition 混合使用的实际场景,特别是在对员工(Employee)分类管理时的多层次分区处理。下面我帮你总结和解析这段流程:

示例场景概览

定义了一个 Employee 结构体继承自 Person,并添加了枚举 EmployeeType 用于标识员工类型:

struct Employee: public Person {enum class EmployeeType {Executive, Manager, Architect, Senior, Junior,Management = Manager,Individual = Architect};EmployeeType type;// 其他成员
};

员工数据示例:

vector<Employee> v{{ "Joe", "P", "Smith", Employee::EmployeeType::Manager },{ "Jane", "Q", "Jones", Employee::EmployeeType::Junior },{ "Frank", "P", "Johnson", Employee::EmployeeType::Architect },{ "Sarah", "B", "Smith", Employee::EmployeeType::Executive },{ "Joe", "X", "Jones", Employee::EmployeeType::Senior },{ "Joe", "A", "Smith", Employee::EmployeeType::Junior },{ "Chris", "M", "Williams", Employee::EmployeeType::Manager }
};

1. 使用 partition_copy 分离管理层和普通员工

vector<Employee> management, individuals;
partition_copy(v.begin(), v.end(), back_inserter(management), back_inserter(individuals), [](const Employee& e) { return e.type <= Employee::EmployeeType::Manager; });
  • management 容器存放 ExecutiveManager 类型(EmployeeType 小于等于 Manager 的)。
  • individuals 容器存放其他类型(如 Architect、Senior、Junior)。
  • 原始序列 v 保持不变。
  • 这里是复制操作。

2. 在 management 中使用 partition 分区,区分执行官和经理

vector<Employee>::iterator car_it = partition(management.begin(), management.end(), [](const Employee& e) { return e.type == Employee::EmployeeType::Executive; });
  • management 容器中所有执行官 (Executive) 放在前面,经理 (Manager) 放后面。
  • 原地操作,容器 management 被重新排序。
  • 返回的迭代器 car_it 指向第一个经理的位置。

3. 在 individuals 中使用 partition 分区,区分建筑师和其他个人员工

vector<Employee>::iterator bike_it = partition(individuals.begin(), individuals.end(), [](const Employee& e) { return e.type == Employee::EmployeeType::Architect; });
  • 建筑师(Architect)被放在前面,其余员工放在后面。
  • 依然是原地操作。

4. 对非建筑师(后面部分)使用 stable_partition 按资历分区

vector<Employee>::iterator old_bike_it = stable_partition(individuals.begin(), individuals.end(), [](const Employee& e) { return e.type <= Employee::EmployeeType::Senior; });
  • 对整个 individuals 容器进行稳定分区,将资历较高的 (Senior 及以上) 放在前面,较低的放后面。
  • 稳定分区保证了已经排好序的建筑师(前面部分)顺序不被破坏。
  • 迭代器 old_bike_it 指向资历较低员工的起始位置。

输出示意(以管理层为例)

Management partitioned:
jet Sarah B Smith: Executive
car Joe P Smith: Manager
car Chris M Williams: Manager
  • 执行官(jet)优先,经理(car)次之。

核心理解

  • partition_copy 用于将输入序列按条件分成两个独立容器,保留原序列不变。
  • partition 对容器进行原地分区,元素被重新排列,分区内顺序不保证稳定。
  • stable_partition 同样原地分区,但保持等价元素的相对顺序,适合在已有分区结果基础上继续细分。

这个内容讲的是 nth_elementpartial_sort 的用法及区别,下面帮你总结并举个简单示例,方便理解:

1. nth_element 基本作用

  • 将序列重排,使得第 nth 个元素就是如果排序后该位置的元素。
  • nth 之前的元素都 <= 它,nth 之后的元素都 >= 它(具体顺序不保证)。
  • 典型应用:快速找到中位数或百分位数。
  • 操作是原地进行,效率高于完全排序。
    示例:找中位数
vector<int> v{12, 2, 89, 78, 18, 7, 72, 69, 81, 50, 49, 50, 51, 49};
size_t nth = v.size() / 2;
nth_element(v.begin(), v.begin() + nth, v.end());
cout << "Median: " << v[nth] << endl;  // 输出中位数

2. partial_sort 基本作用

  • 将序列的前 n 个元素排序好(从小到大或自定义排序)。
  • 后面的元素不保证顺序。
  • 适合需要前 n 个元素有序的情况,比如排行榜。
  • 比完全排序效率高,但比 nth_element 低。
    示例:找前 5 大元素(降序)
vector<int> v{42, 17, 89, 22, 34, 78, 63, 12, 57, 99};
partial_sort(v.begin(), v.begin() + 5, v.end(), greater<int>());
for (int i = 0; i < 5; i++) cout << v[i] << " ";  // 输出前5个最大元素,且有序

3. nth_element + sort vs partial_sort

你也可以用 nth_element 找出前 n 个最大元素的分区位置,再对这部分排序:

vector<int64_t> v = init_vec();
vector<int64_t> v2 = v; // 复制一份
partial_sort(v.begin(), v.begin() + 20, v.end(), greater<int64_t>());
nth_element(v2.begin(), v2.begin() + 20, v2.end(), greater<int64_t>());
sort(v2.begin(), v2.begin() + 20, greater<int64_t>());
// 比较两者排序的前20个元素是否相同(通常相同)
cout << boolalpha << equal(v.begin(), v.begin() + 20, v2.begin()) << endl;
  • 结果:前 20 个元素排序相同(true
  • 但后面的未排序部分可能不同partial_sort 之后尾部顺序不保证,nth_element + sort 的尾部顺序更随机。

总结

算法作用复杂度(平均)是否排序前n元素是否稳定(等价元素顺序)
nth_element找出第 n 个元素,分区O(n)不保证排序
partial_sort排序前 n 个元素O(n log n)
sort完全排序O(n log n)否(除非 stable_sort)

你对 rotate 的理解很正确:

  • rotate 是在 [first, last) 范围内,将 [first, middle) 这一段“左移”到 [middle, last) 之后,实现元素的原地循环移动。
  • 这个操作不需要用 eraseinsert,避免了不必要的内存重新分配和拷贝,效率更高。
  • rotate_copy 和它类似,但会把结果拷贝到另一个容器中,不修改原序列。
  • 需要保证三个迭代器的顺序正确:first <= middle <= last

1. Naïve Implementation(笨方法)用 erase + insert 移动一个元素

vector<int> v = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
vector<int>::iterator it = v.begin() + 2;  // 指向元素2
int i = *it;                              // 取出元素2的值
v.erase(it);                             // 删除元素2,后面的元素都往前移一格
vector<int>::iterator it2 = v.begin() + 6 - 1; // 计算插入位置
v.insert(it2, i);                        // 把2插入到位置5(即原来索引6的位置往前一格)

输出:

before: 0 1 2 3 4 5 6 7 8 9
after:  0 1 3 4 5 2 6 7 8 9

分析:

  • erase 删除元素2,这会导致容器内后续元素左移,容器大小减少1。
  • insert 元素2 到目标位置前面,容器大小变回原来大小,元素又右移。
  • 整体涉及两次数据搬移,效率较低,且可能导致内存重新分配。

2. Better Implementation(更好方法)用 std::rotate 移动一个元素

vector<int> v = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
vector<int>::iterator it = v.begin() + 2;   // 指向元素2
vector<int>::iterator it2 = v.begin() + 6;  // 目标位置后面一个元素
rotate(it, it + 1, it2);

输出:

before: 0 1 2 3 4 5 6 7 8 9
after:  0 1 3 4 5 2 6 7 8 9

分析:

  • rotate(it, it + 1, it2) 是对区间 [it, it2) 执行“左旋1位”:将区间开头元素移到末尾,区间中剩余元素全部向左移动一格。
  • 这里,区间是 [2, 6) 即元素 {2,3,4,5,6}
  • 旋转后变为 {3,4,5,6,2},完成了把 2 移动到原索引6的位置。
  • 只涉及一次元素搬移,效率明显高于第一种方法。
  • 该操作在原地完成,不改变容器大小,不涉及内存重新分配。

3. Rotate a range of items(旋转一个区间的多个元素)

vector<int> v = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
vector<int>::iterator it = v.begin() + 2;    // 指向元素2
vector<int>::iterator it2 = v.begin() + 7;   // 目标区间结束
rotate(it, it + 3, it2);

输出:

before: 0 1 2 3 4 5 6 7 8 9
after:  0 1 5 6 2 3 4 7 8 9

分析:

  • 这里的区间是 [2, 7),包含元素 {2,3,4,5,6}
  • middle 迭代器是 it + 3,即指向元素 5
  • rotate(it, it + 3, it2)[it, it+3) 的元素 {2,3,4} 移到区间末尾,元素 {5,6} 前移。
  • 旋转结果是 {5,6,2,3,4},对应索引2到6变为 5 6 2 3 4
  • 这种用法可以一次性将一段连续元素“整体往后挪”,把另一段挪到前面,非常灵活。

总结它们之间的关系

方法操作对象元素搬移次数容器大小改变备注
笨方法(erase+insert)单个元素两次容器大小变,效率低
std::rotate 单元素一个区间内的单个元素一次原地算法,效率高
std::rotate 区间区间内多个元素一次原地算法,区间灵活旋转

结论

  • 推荐使用 std::rotate 实现元素移动,特别是连续区间内的元素。
  • rotate 是原地算法,操作高效且安全,不改变容器大小。
  • 对比笨方法,避免多次搬移和可能的内存重新分配。

它描述了一种通用的、方向无关的元素移动方式 —— 即:

将容器中一段范围 [first, last) 的元素移动到另一个位置 pos,不管 pos 在原范围的前面还是后面。

核心问题

标准库中的 std::rotate(first, middle, last) 要求:

first <= middle <= last

这意味着:

  • 你必须判断 pos 相对于 [first, last) 的位置,才能决定如何调用 rotate
  • 这对用户来说很容易出错,尤其在不清楚关系的时候。
    因此,Sean Parent 提出一个更易用的抽象:slide

slide 的行为定义

假设有容器:

std::vector<char> v = { 'a', 'b', 'c', 'd', 'e', 'f', 'g' };

我们调用:

slide(v.begin() + 1, v.begin() + 4, v.begin() + 6);

目标是把 [b, c, d) —— 即 {'b', 'c', 'd'} 移动到 f 之前:

原始:  a  b  c  d  e  f  g^  ^
slide(1, 4, 6)
结果:  a  e  b  c  d  f  g^-----^
  • 所选区间 整体前移或后移 到新的位置 pos
  • 保证最终顺序正确。
  • 其底层实现仍然依赖 std::rotate,但封装了逻辑,用户不用关心 first <= pos <= last 的关系。

slide 实现代码(基于 rotate)

template<typename Iterator>
std::pair<Iterator, Iterator> slide(Iterator first, Iterator last, Iterator pos) {if (pos < first) {std::rotate(pos, first, last);return {pos, pos + (last - first)};}if (last < pos) {std::rotate(first, last, pos);return {pos - (last - first), pos};}return {first, last}; // 如果 pos 落在原区间内,不做任何旋转
}

函数返回值意义

std::pair<Iterator, Iterator>:返回新的 [new_first, new_last) 区间,方便后续操作,比如:

auto new_range = slide(...);
std::for_each(new_range.first, new_range.second, highlight);

slide 的实际用途

适用于GUI列表操作、拖拽排序、任务队列重排等场景:

  • 用户选中若干项,拖到另一个位置
  • 无需担心起点和目标位置谁在前
  • 保持逻辑清晰、语义明确

示例完整代码

#include <vector>
#include <algorithm>
#include <iostream>
template<typename Iterator>
std::pair<Iterator, Iterator> slide(Iterator first, Iterator last, Iterator pos) {if (pos < first) {std::rotate(pos, first, last);return {pos, pos + (last - first)};}if (last < pos) {std::rotate(first, last, pos);return {pos - (last - first), pos};}return {first, last};
}
int main() {std::vector<char> v = {'a', 'b', 'c', 'd', 'e', 'f', 'g'};auto result = slide(v.begin() + 1, v.begin() + 4, v.begin() + 5);for (char c : v)std::cout << c << " ";  // 输出: a e b c d f gstd::cout << "\nMoved range: ";for (auto it = result.first; it != result.second; ++it)std::cout << *it << " ";  // 输出: b c d
}

总结理解

特性描述
slide封装了 rotate,自动处理 pos 相对位置
输入参数[first, last) 为要移动的元素范围,pos 为目标插入点
操作方向可以左移,也可以右移,取决于 pos[first, last) 的关系
返回值移动后新位置的 [first, last) 迭代器对
应用场景拖拽移动、列表重排、队列调整、视图刷新等

这是一个很经典的、优雅的“滑动窗口”操作。我们来逐句拆解和图解理解这个版本的 slide 实现

原型回顾:

template <typename I> // I models RandomAccessIterator 
auto slide(I f, I l, I p) -> std::pair<I, I> {if (p < f)return { p, rotate(p, f, l) };if (l < p)return { rotate(f, l, p), p };return { f, l };
}

基本概念:

  • f:要移动区间的开始
  • l:要移动区间的结束(不包括 l
  • p:你想把 [f, l) 插入到的位置
  • 返回值是一个 pair:新的 [f', l') 区间表示移动后的位置

情况 1:目标位置在左边(p < f

if (p < f)return { p, rotate(p, f, l) };

这时你希望把 [f, l) 这个区间往前移动到 p,原位置区间之间的 [p, f) 就是你要腾出的空间。
rotate(p, f, l) 会把 [p, f) 挪到 [l - (f - p), l),而 [f, l) 会移动到 [p, p + (l - f))

示例:

原始:     a b c d e f g
范围:         [c d e]          // f=2, l=5
目标:     ^                    // p=1 (即 'b')
rotate(p, f, l): rotate(1, 2, 5)
变成:     a c d e b f g
新范围:   [1, 4)

所以返回:{p, rotate(p, f, l)} 就是 [1, 4)

情况 2:目标位置在右边(l < p

if (l < p)return { rotate(f, l, p), p };

这时是把 [f, l) 插入到 p 之前,所以 rotate 的区间是 [f, p),中间的 [l, p) 是你要移开的空间。
rotate 会把 [f, l) 移到 [p - (l - f), p),中间的空隙填入 [l, p)

示例:

原始:     a b c d e f g
范围:       [b c d]            // f=1, l=4
目标:             ^            // p=5 (即 'f')
rotate(f, l, p): rotate(1, 4, 5)
结果:     a e f b c d g
新范围:         [2, 5)

返回:{rotate(f, l, p), p} 即:{2, 5}

情况 3:目标位置在内部(p ∈ [f, l)

return { f, l };

这种情况说明你想插入的位置就在你移动的区间内,相当于不需要动,直接返回原区间。

总结:Sean Parent 的 slide 函数要点

情况判断条件旋转调用含义
往前移p < frotate(p, f, l)[f,l) 移到 p 前面,左移
往后移l < protate(f, l, p)[f,l) 移到 p 前面,右移
重叠p ∈ [f, l)不需要移动

std::rotate 小复习:

std::rotate(begin, middle, end)
  • [begin, middle) 移到 [middle, end) 后面
  • 最终顺序:[middle, end) + [begin, middle)
  • 返回新 begin,即 middle

最后的建议

Sean Parent 的这个版本非常精炼,前提是你熟悉 std::rotate 的行为。如果你刚入门,它可能显得太压缩;但一旦你理解 rotate,这个版本是极高效且表达力强的写法。

使用标准库算法组合完成复杂的操作,而不是写显式的循环和特殊处理逻辑

问题:gather 是什么?

把满足条件的一些分散的元素,按照它们原来的相对顺序,集中到一个指定的位置

比如在 GUI 列表中,把你鼠标框选的多个项目聚合到目标位置,而不改变它们之间的顺序。

gather 的目标功能:

  • 输入:一个范围 [f, l),一个谓词 pred 表示选中的项,一个中心位置 p
  • 输出:把所有满足 pred 的元素收集起来,并围绕位置 p 聚合。
  • 返回值:新位置区间(满足 pred 的元素的新位置)

实现方法:利用 stable_partition + rotate

template <typename I, typename Pred>
std::pair<I, I> gather(I f, I l, I p, Pred pred) {return {std::stable_partition(f, p, [&](const auto& x) { return !pred(x); }),std::stable_partition(p, l, pred)};
}

怎么理解这个实现?

核心思想:

把整个序列分成两段:

[f, p)   和   [p, l)

我们希望:

  • [f, p) 这段,把 不是选中的元素 放到前面,让选中的元素移动到接近 p
  • [p, l) 这段,把 选中的元素 放到前面(即靠近 p)。
    最终效果就是:选中的元素都被“吸引”到 p 位置周围。

图解说明

示例:

std::vector<int> v = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
auto pred = [](int x) { return x % 2 == 1; }; // odd numbers
auto result = gather(v.begin(), v.end(), v.begin() + 5, pred);

目标是把所有 奇数1,3,5,7,9)聚集到索引 5 附近。

步骤说明:

  • std::stable_partition(f, p, !pred)
    • 把前半段 [0, 1, 2, 3, 4]不是奇数 的元素(即偶数)移到前面
    • 结果:[0, 2, 4, 1, 3, …]
  • std::stable_partition(p, l, pred)
    • 把后半段 [5, 6, 7, 8, 9] 中的 奇数 移到前面
    • 结果:[0, 2, 4, 1, 3, 5, 7, 9, 6, 8]
      最终聚合结果是:
[非选中 | 选中(聚集) | 非选中]
[0 2 4 | 1 3 5 7 9 | 6 8]

gather 的优势:

  • 不写任何循环
  • 使用标准库 stable_partition 保证原始顺序不变
  • 支持任意选择条件 pred
  • 返回值指示了新聚集区间的位置(pair<I, I>

实战例子(带输出)

#include <vector>
#include <algorithm>
#include <iostream>
template <typename I, typename Pred>
std::pair<I, I> gather(I f, I l, I p, Pred pred) {return {std::stable_partition(f, p, [&](const auto& x) { return !pred(x); }),std::stable_partition(p, l, pred)};
}
int main() {std::vector<int> v = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};auto pred = [](int x) { return x % 2 == 1; };  // odd numbersauto result = gather(v.begin(), v.end(), v.begin() + 5, pred);std::cout << "After gather: ";for (int x : v) std::cout << x << " ";std::cout << "\nGathered range: ";for (auto it = result.first; it != result.second; ++it) std::cout << *it << " ";
}

输出:

After gather: 0 2 4 1 3 5 7 9 6 8 
Gathered range: 1 3 5 7 9

总结:gather 思维模式

特性说明
输入[f, l), 中心 p, 条件 pred
过程[f, p) 保留非选中;对 [p, l) 保留选中
输出把所有满足 pred 的元素聚集到 p 位置周围

原始代码回顾:

template <typename I, // models BidirectionalIterator typename S> // models UnaryPredicate 
auto gather(I f, I l, I p, S s) -> std::pair<I, I> 
{ return { std::stable_partition(f, p, std::not1(s)), std::stable_partition(p, l, s) }; 
}

概念解释

功能:

将序列 [f, l)满足条件 s 的元素,在不改变它们相对顺序的前提下,收集在中心点 p 附近

参数说明:

参数含义
f起始迭代器(inclusive)
l结束迭代器(exclusive)
p聚集中心位置(迭代器)
s一元谓词,判断元素是否应被选中(例如:是否是奇数)
返回值被选中元素的新位置范围 [new_first, new_last)

核心思想:分成两段 partition

将整个区间 [f, l) 分成两段:

[f, p) 和 [p, l)

分别对两段应用 std::stable_partition,这样就可以将所有满足 s 的元素围绕中心 p 稳定地收集起来:

std::stable_partition(f, p, not1(s));  // 把不满足条件的放前面
std::stable_partition(p, l, s);        // 把满足条件的放前面

最终效果:选中的元素都集中在 p 附近。

示例讲解

std::vector<int> v = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
auto is_odd = [](int x) { return x % 2 == 1; };
auto result = gather(v.begin(), v.end(), v.begin() + 5, is_odd);

过程图解:

原始序列:     0 1 2 3 4 | 5 6 7 8 9↑ p = v.begin() + 5
第一段 stable_partition(f, p, not1(s)):
→ 把非奇数的元素 [0, 2, 4] 留在前面,奇数 [1, 3] 被移到 p 前
→ 结果变为:   0 2 4 1 3 | 5 6 7 8 9
第二段 stable_partition(p, l, s):
→ 把奇数 [5, 7, 9] 移到前,结果如下:
→ 最终序列:   0 2 4 1 3 | 5 7 9 6 8↑ gathered at [3, 8)

返回值:

返回的是 std::pair<I, I>,表示聚集后满足条件元素的范围:

result.first = v.begin() + 3; // points to 1
result.second = v.begin() + 8; // points past 9

即:{1, 3, 5, 7, 9} 被集中在 [3, 8)

为何使用 stable_partition

  • 保留原来元素的相对顺序
  • partition 更可控,适合 GUI、用户列表等逻辑
  • 不需要你写显式循环,非常函数式的写法

总结

说明
gather 目的把满足条件的元素集中在某个中心点
用法分别对 [f, p)[p, l) 使用 stable_partition
效果元素按谓词分组、相对顺序不变、围绕 p 聚集
返回值满足条件的元素新范围 [new_first, new_last)

使用 std::set_difference快速对比两个有序集合的差异,用于处理配置更新(比如服务器设置、用户权限、菜单选项等)的一种非常典型而优雅的方法。

使用场景理解

场景:

  • 你有一个 当前配置集合current),是有序的容器(如 std::set)。
  • 系统接收到一份 更新后的配置集合update)。
  • 你想快速知道:
    • 哪些旧配置项不再存在了(→ 要移除
    • 哪些新配置项被添加进来了(→ 要添加

STL 解法:std::set_difference

C++ 提供了专门的算法 std::set_difference

set_difference(a.begin(), a.end(),b.begin(), b.end(),output);

它会把 在 a 中但不在 b 中的元素 放进 output 中。

示例代码讲解

#include <set>
#include <vector>
#include <algorithm>
#include <iostream>
int main() {std::set<std::string> current{ "one", "two", "three", "four", "five" };std::set<std::string> update{ "one", "three", "four", "six", "seven" };std::vector<std::string> removed_items, new_items;// 找出当前配置中被移除的项目(在 current 中,但不在 update 中)std::set_difference(current.begin(), current.end(),update.begin(), update.end(),std::back_inserter(removed_items));// 找出新加入的项目(在 update 中,但不在 current 中)std::set_difference(update.begin(), update.end(),current.begin(), current.end(),std::back_inserter(new_items));// 模拟操作std::cout << "Removed: ";for (const auto& item : removed_items) std::cout << item << " ";std::cout << "\nAdded: ";for (const auto& item : new_items) std::cout << item << " ";
}

输出:

Removed: five two 
Added: seven six

解释原理:

current (旧)update (新)
oneone
two 被移除three
threefour
foursix 被新增
five 被移除seven 被新增

要点总结:

功能方法说明
获取被移除项set_difference(current, update)旧集合中存在,更新中没有的
获取新增项set_difference(update, current)更新中存在,旧集合没有的
容器要求有序(如 setvector 排好序)set_difference 只对有序容器有效
稳定性元素相对顺序不保证但结果是升序

实际应用场景

  • 配置文件更新比对
  • 权限列表(新增/移除权限)
  • GUI 多选项同步
  • 云同步差异分析

Bonus:可读性更高的封装(推荐)

template <typename Container>
std::vector<typename Container::value_type>
difference(const Container& a, const Container& b) {std::vector<typename Container::value_type> result;std::set_difference(a.begin(), a.end(),b.begin(), b.end(),std::back_inserter(result));return result;
}

这样你就可以直接这样调用:

auto removed = difference(current, update);
auto added   = difference(update, current);

C++ STL(标准模板库)算法的分类总览与学习建议

一、总纲:学好 STL 算法的建议

建议说明
熟悉所有算法不仅知道有这些算法,还要知道它们的特点与用途
编写练习代码用小例子尝试每个算法,了解它的行为与边界
改造已有算法学会组合 STL 算法或用自定义谓词适配需求
编写你自己的算法STL 不一定覆盖你所有场景,自己实现是终极理解方式

二、STL 算法总览(按类型分类)

1⃣ 非修改类(Non-Modifying):用于查看/查找/判断

功能代表算法
范围检查all_of, any_of, none_of
遍历操作for_each
查找元素find, find_if, find_if_not, find_end, find_first_of, adjacent_find
数量相关count, count_if
比较元素mismatch, equal, is_permutation
匹配子序列search, search_n
特点:不修改容器内容,只做“观察”。

2⃣ 修改类(Mutating):用于复制、替换、移动、变换

功能代表算法
复制copy, copy_if, copy_n, copy_backward
移动move, move_backward
替换replace, replace_if, replace_copy, replace_copy_if
填充fill, fill_n, generate, generate_n
删除remove, remove_if, remove_copy, remove_copy_if
去重unique, unique_copy
反转/旋转/打乱reverse, rotate, shuffle, random_shuffle
分区partition, stable_partition, partition_copy, partition_point
注意:很多算法“看似”删除,其实只是“重新排列 + 返回末尾”,比如 remove_if + erase 组合成“真正删除”。

3⃣ 排序与相关(Sorting & Set Operations)

功能代表算法
排序sort, stable_sort, partial_sort, partial_sort_copy, nth_element
二分查找lower_bound, upper_bound, binary_search, equal_range
合并merge, inplace_merge
集合操作includes, set_union, set_intersection, set_difference, set_symmetric_difference
堆操作make_heap, push_heap, pop_heap, sort_heap
最小最大min, max, min_element, max_element, minmax, minmax_element
字典序比较lexicographical_compare
排列生成next_permutation, prev_permutation
用于有序结构操作、排序优化、集合比对(如权限、配置差异)等。

4⃣ 数值操作(Numeric)

功能代表算法
累加accumulate
点积inner_product
局部和partial_sum
相邻差adjacent_difference
整数序列生成iota
常用于数学运算、数据分析、图算法等场景。
http://www.xdnf.cn/news/952705.html

相关文章:

  • Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
  • The Trade Desk推出DealDesk,试图让交易ID不再糟糕
  • HTTP 与 TCP 协议的区别与联系
  • 【C++】unordered_set和unordered_map
  • tauri项目,如何在rust端读取电脑环境变量
  • 画质MxPro:优化手游体验,畅享高清画质
  • Linux初步介绍
  • 【VLNs篇】07:NavRL—在动态环境中学习安全飞行
  • 多轮对话实现
  • react更新页面数据,操作页面,双向数据绑定
  • 免费数学几何作图web平台
  • 在阿里云上搭建n8n
  • React Native 弹窗组件优化实战:解决 Modal 闪烁与动画卡顿问题
  • 【Mini-F5265-OB开发板试用测评】1、串口printf输出
  • C++中auto和auto
  • 芯片设计中的通信“动脉”:I2C与I3C IP深度解析
  • ubuntu清理垃圾
  • CTFshow-PWN-栈溢出(pwn48)
  • 【深度学习新浪潮】大模型中,active parameters和total parameters都是什么?
  • “扛不住了就排队!”——聊聊消息队列在高并发系统中的那些硬核用途
  • STM32使用旋转电位器自制调光灯
  • 麒麟系统编译安装QtCreator
  • 01__C++入门
  • 根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
  • 从零手写Java版本的LSM Tree (五):布隆过滤器
  • CppCon 2015 学习:Transducers, from Clojure to C++
  • 蓝桥杯第十届国B 质数拆分
  • 深入解析 ReentrantLock:原理、公平锁与非公平锁的较量
  • DreamO字节开源图像编辑框架
  • IDC智能机房整体解决方案