STL一共提供了四种与set(集合)相关的算法,分别是并集(union)、交集(intersection)、差集(difference)、对称差集(symmetric difference)。
目录
所谓set,可细分为数学上定义的和STL的定义两种,数学上的set允许元素重复而未经排序,例如{1,1,4,6,3},STL的定义(也就是set容器)则要求元素不得重复,并且经过排序,例如{1,3,4,6}。本节的四个算法所接受的set,必须是有序区间(sorted range),元素值可重复出现。换句话说,它们可接受STL的set/multiset容器作为输入区间。
SGI另外提供了hash_set/hash_multiset两种容器,以hashtable为底层机制。其内的元素并未呈现排序状态,所以虽然名称也是set字样,缺不可以应用于本节的四个算法。
本节四个算法都至少有四个参数,分别表现两个set区间,以下所有说明都以S1代表第一区间[first1, last1),以S2代表第二区间[first2, last2).每一个set算法都提供两个版本,第二个版本允许用户指定"a<b"的意义,因为这些算法判断两个元素是否相等的依据,完全靠"小于"运算。
a <b && b< a 推导出 a==b
以下程序测试四个set相关算法。欲使用它们,必须包含<algorithm>
cpp
#include <set>
#include <iostream>
#include <algorithm>
#include <iterator>
using namespace std;
template <class T>
struct display {
void operator() (const T& x) {
cout << x << " ";
}
};
int main() {
int ia1[6] = {1, 3, 5, 7, 9, 11};
int ia2[7] = {1, 1, 2, 3, 5, 8, 13};
multiset<int> S1(ia1, ia1+6);
multiset<int> S2(ia2, ia2+7);
for_each(S1.begin(), S1.end(), display<int>());// 1 3 5 7 9 11
cout << endl;
for_each(S2.begin(), S2.end(), display<int>());// 1 1 2 3 5 8 13
cout << endl;
auto first1 = S1.begin();
auto last1 = S1.end();
auto first2 = S2.begin();
auto last2 = S2.end();
cout << "Union of S1 and S2:";
set_union(first1, last1, first2, last2, ostream_iterator<int>(cout, " ")); // 1 1 2 3 5 7 9 11 13
cout << endl;
first1 = S1.begin();
first2 = S2.begin();
cout << "intersection of S1 and S2:";
set_intersection(first1, last1, first2, last2, ostream_iterator<int>(cout, " "));// 1 3 5
cout << endl;
first1 = S1.begin();
first2 = S2.begin();
cout << "difference of S1 and S2 (S1-S2): " ;
set_difference(first1, last1, first2, last2, ostream_iterator<int>(cout, " ")); // 7 9 11
cout << endl;
first1 = S1.begin();
first2 = S2.begin();
cout << "Symmetric difference of S1 and S2: " ;
set_symmetric_difference(first1, last1, first2, last2, ostream_iterator<int>(cout, " ")); // 1 2 7 8 9 11 13
cout << endl;
first1 = S1.begin();
first2 = S2.begin();
cout << "difference of S2 and S1 (S2-S1): ";
set_difference(first2, last2, first1, last1, ostream_iterator<int>(cout, " ")); // 1 2 8 13
cout << endl;
return 0;
}
执行结果如下:
cpp
1 3 5 7 9 11
1 1 2 3 5 8 13
Union of S1 and S2:1 1 2 3 5 7 8 9 11 13
intersection of S1 and S2:1 3 5
difference of S1 and S2 (S1-S2): 7 9 11
Symmetric difference of S1 and S2: 1 2 7 8 9 11 13
difference of S2 and S1 (S2-S1): 1 2 8 13
请注意,当集合(set)允许重复元素的存在时,并集、交集、差集、对称差集的定义,都与直观定义有些微的不同。例如上述的并集结果,我们会直观以为是{1,2,3,5,7,8,9,11,13},而上述的对称差结果,我们会直观以为是{2,7,8,9,11,13},这都是未考虑重复元素的结果。以下各小节会详细说明。
set_union
算法set_union可构造S1、S2之并集。也就是说,它能构造出集合,此集合内含S1或S2内的每一个元素。S1、S2及其并集都是以排序区间表示。返回值为一个迭代器,指向输出区间的尾端。
由于S1和S2内的每一个元素都不需要唯一,因此,如果某个值在S1出现n次,在S2出现m次,那么该值在输出区间会出现max(n,m)次,其中n个来自S1,其余来自S2,在STL set容器内,且
set_union是一种稳定(stable)操作,意思是输入区间内的每个元素的相对顺序都不会改变。set_union有两个版本,差别在于如何定义某个元素小于另一个元素。第一版本使用operator<进行比较,第二版本采用仿函数comp进行比较。
cpp
template <class InputIterator1, class InputIterator2, class OutputIterator>
OutputIterator set_union(InputIterator1 first1, InputIterator1 last1,
InputIterator2 first2, InputIterator2 last2,
OutputIterator result) {
while (first1 != last1 && first2 != last2) {
if (*first1 < *first2) {
*result = *first1;
++first1;
} else if (*first2 < *first1) {
*result = *first2;
++first2;
} else {
*result = *first1;
++first1;
++first2;
}
++result;
}
// 此时[first1,last1)和[first2,last2)至少有一个空白区间,边界剩下的是都属于union的元素
return copy(first2, last2, copy(first1, last1, result));
}
从以上合并的过程,可以看出主要是利用了set/multi_set经过排序的特性,而后对并集的特例化处理。和数学定义上的union处理略有差异。
set_itersection
算法set_itersection可构造S1,S2之交集。也就是说,它能构造出集合,此集合内含同时出现在S1和S2的每一个元素。S1、S2及其交集都是以排序区间表示。返回值为一个迭代器,指向输出区间的尾端。
由于S1和S2内的每个元素都不需要唯一,因此,如果某个值在S1出现n次,在S2出现m次,那么该值在输出区间出现min(n,m)次,并且全部来自S1.在STL set内,且。
set_itersection是一种稳定(stable)的操作,意思是输出区间内的每个元素的相对顺序都和S1内的相对顺序相同。它有两个版本,其差异同set_union.其代码定义如下:
cpp
template <class InputIterator1, class InputIterator2, class OutputIterator>
OutputIterator set_itersection(InputIterator1 first1, InputIterator1 last1,
InputIterator2 first2, InputIterator2 last2,
OutputIterator result) {
while (first1 != last1 && first2 != last2) {
if (*first1 < *first2) {
++first1;
} else if (*first2 < *first1) {
++first2;
} else { // *first1 == *first2
*result = *first1;
++first1;
++first2;
}
++result;
}
return result;
}
对照set_itersection和set_union的算法实现,可发现内部迭代器的前进逻辑是一致的;只是输出时,只讲*first1==*first2的数据输出到result。对于边界情况,因为其中一组迭代器已经为空,所以也就没有了新的元素加入,直接返回了result;
set_difference
算法set_difference可构造S1、S2只差集。也就是说,它能构造集合S1-S2,此集合内容"出现于S1但不出现于S2"的每一个元素。S1、S2及其交集都是以排序区间表示。返回值为一个迭代器,指向输出区间的尾端。
由于S1和S2内的每个元素都不需要唯一,因此,如果某个值在S1出现n次,在S2出现m次,那么该值在输出区间出现max(n-m,0)次
set_difference是一种稳定操作,输出相对顺序保持S1内的相对顺序。代码如下:
cpp
template <class InputIterator1, class InputIterator2, class OutputIterator>
OutputIterator set_difference(InputIterator1 first1, InputIterator1 last1,
InputIterator2 first2, InputIterator2 last2,
OutputIterator result) {
while (first1 != last1 && first2 != last2) {
if (*first1 < *first2) {
*result = *first1;
++first1;
++result;
} else if (*first2 < *first1) {
++first2;
} else {
++first1;
++first2;
}
}
return copy(first1, last1, result);
}
有了前面set_union及set_itersecion算法的经验,可看出迭代器的逻辑还是保留的相同的步伐。只是输出给result的值,只保留了*first1 < *first2的部分;正好符合S1-S2的特性。
set_symmetric_difference
算法set_symmetric_difference可构造S1、S2之对称差集。也就是构造出,此集合内含"出现在S1但不出现于S2"以及"出现在S2不出现在S1"的每一个元素。
有了前三个算法的基础,很容易得出set_symmetric_difference的结果包含*first1<*first2,以及*first2-*first1的结果,同时需要将边界情况保留。其代码如下:
cpp
template <class InputIterator1, class InputIterator2, class OutputIterator>
OutputIterator set_symmetric_difference(InputIterator1 first1, InputIterator1 last1,
InputIterator2 first2, InputIterator2 last2,
OutputIterator result) {
while (first1 != last1 && first2 != last2) {
if (*first1 < *first2) {
*result = *first1;
++first1;
++result;
} else if (*first2 < *first1) {
*result = *first2
++first2;
++result;
} else {
++first1;
++first2;
}
}
return copy(first2, last2, copy(first1, last1, result));
}
参考文档《STL源码剖析》---侯捷