【STL】关联式容器 - Set和Multiset

Set和Multiset

STL提供的vector,list和deque属于序列式容器即可序群集,每个元素均有固定的位置,取决于插入时机和地点,和元素值无关。如果以追加的方式对一个群集置入六个元素,它们的排列次序将和置入次序一致。

而关联式容器为已序群集,元素位置取决于特定的排序准则。如果讲六个元素置入群集中,它们的位置取决于元素值,与插入次序无关。STL提供了四个关联式容器:set,multiset,map和multiset。

set和mulitisets会根据特定的排序准则,自动将元素排序。两者不同点在于multisets允许元素重复,而set不允许。

set和multiset的结构:

image

在使用set或multiset前,必须先引入头文件:

#include <set>

在这个头文件中,上述两个型别都被定义为命名空间std内的class templates:

namespace std{
    template <class T,
              class Compare = less<T>,
              class Allocator = allocator<T> >
    class set;

    template <class T,
              class Compare = less<T>,
              class Allocator = allocator<T> >
    class multiset;
}

Set和Multiset的能力

与所有标准关联式容器类似,set和multiset通常以平衡二叉树完成。

Set和Multisets的内部结构:

image

自动排序的主要优点在于使用二叉树搜寻元素时具有良好的性能。搜寻函数算法具有对数的复杂度。在拥有1000个元素的set或multiset中搜寻元素,二叉树搜寻动作的平均时间为线性搜寻事件的1/50。

但是自动排序造成set和multiset的一个重要限制:不能直接改变元素值,因此,要改变元素值,必须先删除旧元素,再插入新元素。

  • set和multiset不提供用来存取元素的任何操作函数
  • 通过迭代器进行元素间接存取

Set和Multiset的操作函数

set和multiset的构造和析构函数

操作 效果
set c 产生一个空的set/multiset,其中不含任何元素
set c(op) 以op为排序准则,产生一个空的set/multiset
set c1(c2) 产生某个set/multiset的副本,所有元素均被复制
set c(begin,end,op) 以区间[begin,end]内的元素产生一个set/multiset
c.~set() 销毁所有元素,释放内存

非变动性操作(查询大小,相互比较)

操作 效果
c.size() 返回容器的大小
c.empty() 判断容器大小是否为空
c.max_size() 返回可容纳的最大元素数量
c1 == c2 判断c1是否等于c2
c1 != c2 判断是否c1不等于c2

元素比较动作只能用于型别相同的容器,否则编译阶段就会报错。

特殊的搜寻函数

set和multiset在元素快速搜寻方面有优化设计,所以提供了特殊的搜寻函数,使用这些优化算法,可以获得对数复杂度,由于非STL算法的线性复杂度

操作 效果
count(elem) 返回"元素值为elem"的元素个数
find(elem) 返回"元素值为elem"的第一个元素。如果找不到就返回end()
lower_bound(elem) 返回elem的第一个可安插位置,即"元素值>=elem"的第一个元素位置
upper_bound(elem) 返回elem的最后一个可安插位置,即"元素值>elem"的第一个元素位置
equal_range(elem) 返回elem的可安插的第一个位置和最后一个位置,即"元素值==elem"的元素区间

实例:

#include <set>
#include <iostream>

using namespace std;

int main()
{
    set<int> c;

    c.insert(1);
    c.insert(2);
    c.insert(3);
    c.insert(4);
    c.insert(5);

    cout<<"lower_nound(3):" << *c.lower_bound(3) <<endl;
    cout<<"upper_bound(3):" << *c.upper_bound(3) <<endl;
    cout<< "c.equal_range(3):" << *c.equal_range(3).first << " "
              << *c.equal_range(3).second <<endl;

    cout<<endl;

    cout<<"lower_nound(5):" << *c.lower_bound(5) <<endl;
    cout<<"upper_bound(5):" << *c.upper_bound(5) <<endl;
    cout<< "c.equal_range(5):" << *c.equal_range(5).first << " "
              << *c.equal_range(5).second <<endl;
}

运行结果:

image

赋值

在赋值的操作函数中,赋值操作的两端容器必须具有相同型别

操作 效果
c1 = c2 将c2中所有元素赋值给c1
c1.swap(c2) 将c1和c2元素互换
swap(c1,c2) 同上。属于全局函数

迭代器相关函数

set和multiset不提供元素直接存取,所以只能采用迭代器。

操作 效果
c.begin() 返回一个双向迭代器,指向第一个元素
c.end() 返回一个双向迭代器,指向最后元素的下一位置
c.rbegin() 返回一个逆向迭代器,指向逆向遍历时的第一个元素
c.rend() 返回一个逆向迭代器,指向逆向遍历时的最后元素的下一位置

这里的迭代器是双向迭代器,所以对于只能用于随机存取迭代器的STL算法,set和multiset就无法使用了

元素的安插和移除

按照STL惯例,必须保证参数有效,迭代器必须指向有效位置,序列起点不能位于终点之后,不能从空容器中删除元素

插入和移除多个元素时,单一调用(一次处理)比多次调用(逐一处理)快得多

操作 效果
c.insert(elem) 插入一份elem副本,返回新元素位置
c.insert(pos,elem) 在pos位置插入一份elem副本,返回新元素位置
c.insert(beg,end) 在区间[beg,end]内所有元素的副本安插到c
c.erase(elem) 移除"与elem相等的所有元素",返回被移除的元素个数
c.erase(pos) 移除迭代器pos位置的元素,无返回值
c.erase(beg,end) 移除区间[beg,end]内的所有元素,无返回值
c.clear() 移除全部元素,将整个容器清空

插入函数的返回值型别不完全相同:

  • set提供接口:
pair<iterator,bool> insert(const value_type& elem);
iterator insert(iterator pos_hint,const value_type& elem);

iterator erase(iterator pos);
iterator erase(iterator beg,iterator end);
  • multiset提供接口:
iterator insert(const value_type& elem);
iterator insert(iterator pos_hint,const value_type& elem);

void erase(iterator pos);
void erase(iterator beg,iterator end);

返回值类型不同的原因是,mulitisets允许元素重复,而set不允许。所以如果将某元素安插至一个set内,该set已经内含同值元素,则安插操作失败。

异常处理

set和multiset是以节点为基础的容器,如果节点构造失败,容器仍保持原样。此外由于析构函数并不抛出异常,所以节点的移除不可能失败。

set和multiset实例:

展示set的一些能力

#include <iostream>
#include <set>
#include <iterator>

using namespace std;

int main()
{
    // 定义一个set,容纳降序排列的int
    typedef set<int,greater<int> > IntSet;

    IntSet coll1;

    // 插入元素到random order
    coll1.insert(4);
    coll1.insert(3);
    coll1.insert(5);
    coll1.insert(1);
    coll1.insert(6);
    coll1.insert(2);
    coll1.insert(5);   // 此插入动作会被忽略,因为set不允许数值重复的元素

    // iterate over all elements and print them
    IntSet::iterator pos;
    for(pos = coll1.begin();pos != coll1.end();++pos)
    {
        cout<<*pos<<' ';
    }
    cout<<endl;

    // insert 4 again and process return value
    pair<IntSet::iterator,bool> status = coll1.insert(4);
    if(status.second)
    {
        cout<<"4 inserted as element"
            << distance(coll1.begin(),status.first) +1
            <<endl;
    }
    else
    {
        cout<<"4 already exists" <<endl;
    }

    // 产生一个新的set,容纳升序排列的ints,并以原本那个sets元素作为初值
    set<int> coll2(coll1.begin(),coll1.end());


    // print all elements of the copy
    copy(coll2.begin(),coll2.end(),ostream_iterator<int>(cout," "));
    cout<<endl;


    // 移除数值为3的元素之前的所有元素
    coll2.erase(coll2.begin(),coll2.find(3));

    // 所有数值为5的元素都被移除
    int num;
    num = coll2.erase(5);
    cout<<num<<" elements removed"<<endl;

    // 打印所有的元素
    copy(coll2.begin(),coll2.end(),ostream_iterator<int>(cout," "));
    cout <<endl;

}

运行结果:

image














参考文章:
C++标准程序库

posted @ 2022-10-06 21:41  Emma1111  阅读(80)  评论(0)    收藏  举报