C++之STL的algorithm(7)之集合运算相关算法(交集、并集、差集)整理
注:整理一些突然学到的C++知识,随时mark一下
例如:忘记的关键字用法,新关键字,新数据结构
C++ 的集合算法整理
提示:本文为 C++ 中set_intersection、set_union和set_difference的写法和举例
一、 集合相关操作算法
集合算法即对两个容器求交、并、补的数学运算。set_intersection、set_union和set_difference算法被定义在<algorithm>
头文件中,它们用于计算两个有序序列之间的交集、并集和差集。这些算法通常与std::vector、std::list、std::set、std::multiset等容器配合使用,但也可以用于其他有序序列。请注意,对于std::map和std::multimap这类键值对容器,这些算法也是适用的,因为它们内部是有序的。
1、set_intersection算法求交集
将两个容器(有序,set自动排序)的交集赋给第三个容器。
语法:
template< class InputIt1, class InputIt2, class OutputIt >
OutputIt set_intersection( InputIt1 first1, InputIt1 last1,
InputIt2 first2, InputIt2 last2,
OutputIt d_first );
参数:
first1, last1:
指向第一个输入序列的起始和结束迭代器。
first2, last2:
指向第二个输入序列的起始和结束迭代器。
d_first:
指向输出序列的起始迭代器。
返回:
指向输出序列中最后一个元素的下一个位置的迭代器。
举例:
#include <iostream>
#include <vector>
#include <set>
#include <algorithm>
int main() {
std::vector<int> v1 = {1, 2, 3, 4, 5};
std::vector<int> v2 = {4, 5, 6, 7, 8};
std::vector<int> v_intersection;
std::set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), std::back_inserter(v_intersection));
for (int num : v_intersection) {
std::cout << num << ' ';
}
// 输出: 4 5
// 对于set
std::set<int> s1 = {1, 2, 3, 4, 5};
std::set<int> s2 = {4, 5, 6, 7, 8};
std::set<int> s_intersection;
std::set_intersection(s1.begin(), s1.end(), s2.begin(), s2.end(), std::inserter(s_intersection, s_intersection.begin()));
for (int num : s_intersection) {
std::cout << num << ' ';
}
// 输出: 4 5
}
2、set_union算法求并集
将两个容器(有序,set自动排序)的并集赋给第三个容器。
语法:
template< class InputIt1, class InputIt2, class OutputIt >
OutputIt set_union( InputIt1 first1, InputIt1 last1,
InputIt2 first2, InputIt2 last2,
OutputIt d_first );
参数:
与set_intersection相同。
返回:
与set_intersection相同。
举例:
#include <iostream>
#include <vector>
#include <set>
#include <algorithm>
int main() {
std::vector<int> v1 = {1, 2, 3, 4, 5};
std::vector<int> v2 = {4, 5, 6, 7, 8};
std::vector<int> v_union;
std::set_union(v1.begin(), v1.end(), v2.begin(), v2.end(), std::back_inserter(v_union));
for (int num : v_union) {
std::cout << num << ' ';
}
// 输出: 1 2 3 4 5 6 7 8
// 对于set
std::set<int> s1 = {1, 2, 3, 4, 5};
std::set<int> s2 = {4, 5, 6, 7, 8};
std::set<int> s_union;
std::set_union(s1.begin(), s1.end(), s2.begin(), s2.end(), std::inserter(s_union,
s_union.begin()));
for (int num : s_union) {
std::cout << num << ' ';
}
// 输出: 1 2 3 4 5 6 7 8
}
3、set_difference算法求差集
将两个容器(有序,set自动排序)的差集赋给第三个容器。差集A-B即集合A减去AB的交集。
语法:
template< class InputIt1, class InputIt2, class OutputIt >
OutputIt set_difference( InputIt1 first1, InputIt1 last1,
InputIt2 first2, InputIt2 last2,
OutputIt d_first );
参数:
与set_intersection相同。
返回:
与set_intersection相同。
举例:
#include <iostream>
#include <vector>
#include <set>
#include <algorithm>
int main() {
std::vector<int> v1 = {1, 2, 3, 4, 5};
std::vector<int> v2 = {4, 5, 6, 7, 8};
std::vector<int> v_difference;
std::set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), std::back_inserter(v_difference));
for (int num : v_difference) {
std::cout << num << ' ';
}
// 输出: 1 2 3
// 对于set
std::set<int> s1 = {1, 2, 3, 4, 5};
std::set<int> s2 = {4, 5, 6, 7, 8};
std::set<int> s_difference;
std::set_difference(s1.begin(), s1.end(), s2.begin(), s2.end(), std::inserter(s_difference, s_difference.begin()));
for (int num : s_difference) {
std::cout << num << ' ';
}
// 输出: 1 2 3
}
二、关于参数中的 back_inserter 和 inserter
在上面的例子中,std::back_inserter
是一个迭代器适配器,它用于在容器的尾部插入元素。当你调用 set_intersection、set_union 或 set_difference 等算法时,这些算法需要一个输出迭代器,用于指向你想要插入结果的容器的位置。
对于 std::vector,使用 std::back_inserter 是非常合适的,因为 std::vector 是一个动态数组,它允许在尾部高效地添加元素。std::back_inserter 内部持有一个指向 vector 的引用,并使用 push_back 成员函数来添加新元素。因此,当你通过 std::back_inserter 插入元素时,vector 会自动调整其大小以容纳新元素。
这是使用 std::back_inserter 的一个例子:
std::vector<int> result;
std::set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), std::back_inserter(result));
在这个例子中,set_intersection 算法会将交集的结果插入到 result 向量的尾部。每次算法找到一个交集元素时,它就会调用 std::back_inserter 的 operator=
,这实际上会调用 result.push_back() 来添加新元素。
相比之下,如果你直接传递 result.begin() 作为输出迭代器,那么算法会尝试直接在 result 的开始位置写入元素,这通常会导致未定义行为,因为 result 可能没有足够的空间来容纳所有结果,或者算法可能会覆盖掉 result 中已有的元素。
对于 std::set 和 std::multiset,情况稍有不同。这些容器不允许在任意位置插入元素,而只允许在特定的位置(根据元素的排序顺序)插入元素。因此,对于 set 和 multiset,通常使用 std::inserter 而不是 std::back_inserter,并将迭代器指向容器的 begin(),因为 std::inserter 使用 insert 成员函数来添加元素,这允许在正确的位置插入元素而保持容器的排序状态。
例如:
std::set<int> result;
std::set_intersection(s1.begin(), s1.end(), s2.begin(), s2.end(), std::inserter(result, result.begin()));
在这个例子中,std::inserter 使用 result.insert() 来在正确的位置插入交集元素,从而保持 result 的排序状态。
总之,选择使用 std::back_inserter 还是 std::inserter 取决于你正在使用的容器类型以及你希望如何插入元素。对于 std::vector,通常使用 std::back_inserter;对于 std::set 和 std::multiset,则使用 std::inserter。
总结
对于std::vector
,上述算法均假设vector已经排序。如果vector未排序,需要先对其进行排序,否则结果将是不正确的。
对于std::set
和std::multiset
,由于它们内部自动维护排序,因此可以直接使用这些算法。
对于std::map
和std::multimap
,这些算法同样适用,因为它们也是有序的。