STL算法之set相关算法

STL一共提供了四种与set(集合)相关的算法,分别是并集(union)、交集(intersection)、差集(difference)、对称差集(symmetric difference)。

目录

set_union

set_itersection

set_difference

set_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源码剖析》---侯捷

相关推荐
Dream_Snowar40 分钟前
速通Python 第三节
开发语言·python
唐诺1 小时前
几种广泛使用的 C++ 编译器
c++·编译器
高山我梦口香糖2 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
冷眼看人间恩怨2 小时前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
信号处理学渣2 小时前
matlab画图,选择性显示legend标签
开发语言·matlab
红龙创客2 小时前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
Lenyiin2 小时前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
jasmine s2 小时前
Pandas
开发语言·python
biomooc2 小时前
R 语言 | 绘图的文字格式(绘制上标、下标、斜体、文字标注等)
开发语言·r语言
骇客野人3 小时前
【JAVA】JAVA接口公共返回体ResponseData封装
java·开发语言