zoukankan      html  css  js  c++  java
  • C++基础之关联容器

    关联容器

    关联容器和顺序容器的本质区别:关联容器是通过键存取和读取元素、顺序容器通过元素在容器中的位置顺序存储和访问元素。因此,关联容器不提供front、push_front、pop_front、back、push_back以及pop_back,此外对于关联容器不能通过容器大小来定义,因为这样的话将无法知道键所对应的值什么。关联容器支持高效的关键字查找和访问,主要是map和set两类。标准库提供了8个关联容器:

    map 关联数组:保存键值对,每个元素是pair类型,关键字起到索引的作用,值则表示与索引相关联的数据
    set 大小可变的集合,支持通过键实现的快速读取,只有关键字,没有值
    multimap 支持同一个键多次出现的 map 类型
    multiset 支持同一个键多次出现的 set 类型
    unordered_map 使用hash函数组织的map
    unordered_set 使用hash函数组织的set
    unordered_multimap 使用hash函数组织的map,关键字可重复出现
    unordered_multiset 使用hash函数组织的set,关键字可重复出现

    一个map或set中关键字必须是唯一的,即一个关键字对应唯一一个值,但是multimap或multiset没有此限制,他们允许多个值对应一个关键字。

    有序容器的关键字类型,默认情况下使用<运算符来比较关键字,也可以向算法提供自己定义的操作,但是操作必须在关键字类型上定义一个严格弱序,尖括号中每个类型仅仅表示一个类型,当创建对象时,才会以构造函数参数的形式提供真正的比较操作。

    typedef struct Sales{
        int id;
    };
    //自定义的比较函数
    bool compareId(const Sales &s1, const Sales &s2){
        return s1.id < s2.id;
    }
    //将decltype作用于函数时,返回函数类型,而非函数指针,因此要显示加*来指示它是函数指针;使用compareId来初始化对象表示,使用bookstore添加对象是,使用该函数来排序
    multiset<Sales, decltype(compareId)*>bookstore(compareId);

    pair类型

    pair类型定义在utility头文件中。

    pair<T1, T2> p1; 创建一个空的 pair 对象,它的两个元素分别是T1 和 T2 类型,采用值初始化
    pair<T1, T2> p1(v1, v2); 创建一个 pair 对象,它的两个元素分别是 T1 和 T2 ,其中 first 成员初始化为 v1,而 second 成员初始化为 v2
    pair<T1, T2> p1={v1, v2}; 等价于p1(v1, v2);新增加
    make_pair(v1, v2) 以 v1 和 v2 值创建一个新 pair 对象,其元素类型分别是 v1 和 v2 的类型
    p1 < p2 两个 pair 对象之间的小于运算,其定义遵循字典次序:如果 p1.first < p2.first 或者 !(p2.first < p1.first) && p1.second < p2.second,则返回
    true
    p1 == p2 如果两个 pair 对象的 first 和 second 成员依次相等,则这两个对象相等。该运算使用其元素的 == 操作符
    p.first 返回 p 中名为 first 的(公有)数据成员
    p.second 返回 p 的名为 second 的(公有)数据成员

    关联容器还定义了类型:key_type:此容器类型的关键字类型;mapped_type:每个关键字关联的类型,只适用于map;value_type:对于set,与key_type相同,对于map,为pair<const key_type, mapped_type>。

    注意:

    • map的value_type是一个pair,我们可以改变pair的值,但不能改变关键字的值。
    • 虽然set定义了iterator和const_iterator类型,但是这两种类型都只能读set中的元素,而不允许改变它的值。

    我们通常不对关联容器使用泛型算法。因为,首先关键字是const表示不能将它传递给修改或重排容器元素的算法;虽然关联容器可用于只读算法,但是,这类算法很多要搜索序列,而关联容器中元素不能通过关键字进行快速查找(O(1)),因此,对其使用泛型搜索算法几乎总是坏主意。例如,泛型算法中的find,它会顺序搜索,但是关联容器定义了专用的find成员函数,它的效率会比泛型算法find快很多。

     给容器添加元素

    使用insert成员向容器添加元素或元素范围。

    vector<int> arr = {0,1,2,3,4};
    set<int> st;//空集合
    st.insert(arr.begin(),arr.end());//添加一个范围
    st.insert(5);//添加一个元素
    map<string,int>mp;
    mp.insert({"hh",1});
    mp["tt"] = 3;

    insert返回的值依赖于容器类型和参数。对于不包含重复关键字的容器,添加单一元素的insert和emplace返回一个pair,pair的第一个元素是指向给定关键字的迭代器,另一个是bool类型的变量,表明插入成功与否,如果元素已存在,insert什么也不做,返回值bool部分为false。

    m.insert(e) e 是一个用在 m 上的 value_type 类型的值。如果键(e.first)不在 m 中,则插入一个值为 e.second 的新元素;如果该键在 m 中已存在,则保持 m 不变。该函数返回一个 pair 类型对象,包含指向键为 e.first 的元素的 map 迭代器,以及一个 bool 类型的对象,表示是否插入了该元素
    m.insert(beg,end) beg 和 end 是标记元素范围的迭代器,其中的元素必须为 m.value_type 类型的键-值对。对于该范围内的所有元素,如果它的键在 m 中不存在,则将该键及其关联的值插入到 m。返回void 类型
    m.insert(iter, e) e 是一个用在 m 上的 value_type 类型的值。如果键(e.first)不在 m 中,则创建新元素,并以迭代器 iter 为起点搜索新元素存储的位置。返回一个迭代器,指向 m 中具有给定键的元素

    统计单词出现次数的程序:

    map<string, int> word_count;
    string word;
    while (cin >> word)
    {
        auto ret = word_count.insert({ word, 1 });
        if (!ret.second)//如果没插入成功,证明原来已经存在键值,将统计值+1
        {
            ++ret.first->second;// first是一个迭代器,指向插入的键
        }
    }

    map的下标操作

    map和unordered_map提供下标操作和对应的函数at。set不支持下标操作,因为没有与关键字关联的"值",multimap和unordered_multimap也没有下标操作,因为有多个值与一个关键字关联。

    使用下标访问 map 与使用下标访问数组或 vector 的行为截然不同:用下标访问不存在的元素将导致在 map 容器中添加一个新元素,它的键即为该下标值。通常来说,下标操作符返回左值。它返回的左值是特定键所关联的值。

    有别于 vector 或 string 类型,map 下标操作符返回的类型与对 map 迭代器进行解引用获得的类型不相同。显然,map 迭代器返回 value_type 类型的值——包含 const key_type 和 mapped_type 类型成员的 pair 对象;下标操作符则返回一个 mapped_type 类型的值。

    删除元素

    • c.erase(k) 删除m中键为k的元素。返回值为被删除元素的个数,对于map容器而言,其值必然是0或1。
    • c.erase(p) 从m中删除迭代器p所指向的元素。返回值为void类型。
    • c.erase(b,e) 从m中删除一段由一对迭代器范围的元素。返回值为void类型。

    访问元素

    关联容器提供多种查找指定元素的方法。如果只关心元素是否在容器中,使用find最好。对于不允许重复关键字的容器,find和count没有什么区别;但是对于允许重复关键字的容器,count可以同具有相同关键字的元素的个数。

    下面是关联容器中查找元素的操作:(lower_bound和upper_bound不适用于无序容器)

    c.find(k) 返回一个迭代器,指向第一个关键字为k的元素,若k不存在,则返回尾后迭代器
    c.count(k) 返回关键字为k的元素的数量;不允许重复关键字的容器,返回值永远是0或1
    c.lower_bound(k) 返回一个迭代器,指向第一个关键字不小于k的元素
    c.upper_bound(k) 返回一个迭代器,指向第一个关键字大于k的元素
    c.equal_range(k) 返回一个迭代器pair,表示关键字等于k的范围,若k不存在,pair的两个成员均等于c.end()

    由于map中下标操作会将不存在的关键字插入到map中,当我们不需要查找时去插入关键字时,就不能使用它,此时需要使用find函数。

    如果一个multimap或multiset中有多个元素具有给定关键字,则这些元素在容器中会相邻存储。此时我们要找到某个关键字的所有元素时,有下面三种方法:

    方法一:通过count和find来找到关键字对应的所有元素

    //建立作者及其作品的容器
        while(true){
            string author, work;
            cout << "enter authors name or end the input" << endl;
            if(!getline(cin, author))break;
            cout << "enter authors works" << endl;
            while (cin >> work)authors.insert(make_pair(author, work));
            cin.clear();
        }
        //需要查找的关键字(作者)
        string search_item("Alain de Botton");
    
        //统计该关键字中元素的个数,即作品数
        auto entries = authors.count(search_item);
        //作者的第一部作品
        auto iter = authors.find(search_item);
    
        //循环找到所有作品
        while (entries){
            cout << iter->second << endl;//输出每个作品
            ++iter;
            --entries;
        }

    方法二:通过lower_bound和upper_bound来找到关键字对应的所有元素

    lower_bound返回第一个具有该关键字的元素位置,upper_bound返回最后一个与该关键字匹配的元素位置之后的一个位置。如果没有该关键字,则两个函数返回值相同;当关键字大于容器中的任何一个关键字,则会返回尾后迭代器。

    //循环找到所有作品
        for (auto be = authors.lower_bound(search_item), ed = authors.upper_bound(search_item); be != ed; ++be){
            cout << be->second << endl;//输出每个作品
        }

    方法三:通过equal_range来找到关键字对应的所有元素

    本质上和第二个方法相同。

    //循环找到所有作品
        for (auto range = authors.equal_range(search_item); range.first != range.second; ++range.first){
            cout << range.first->second << endl;//输出每个作品
        }

    单词转换程序的实例:http://blog.csdn.net/jy_95/article/details/47807789

    无序容器

    新标准定义了四个无序容器:unordered开头的容器,使用hash函数来构造。

    无序容器也支持与有序容器相同的操作,例如insert和find;因此通常可以使用无序容器来替换有序容器。

    类    型
    X::hasher Hash,一个这样的二元函数对象,即如果hf的类型为Hash,k的类型为Key,则hf(k) 的类型为std::size_t
    X::local_iterator 一个类型与X::iterator相同的迭代器,但只能用于一个桶
    X::const_local_iterator 一个类型与X::const_iterator相同的迭代器,但只能用于一个桶

    管理桶

    由于无序关联容器的实现是基于hash函数,那么就需要解决冲突问题,解决冲突的办法比较常用有开放地址法和拉链法。在C++的unordered_map当中,采用的是”桶”的方法,也就是出现冲突的元素都存放在一个桶里。

    无序容器存储上组织为一个桶,可以存放零个或多个元素(因此,不同关键字的元素也可能在一个桶中,此时定位到那个桶后,在顺序搜索桶中的元素来找到目标元素),如果允许重复关键字,则关键字相同的元素也存放在一个桶中。因此,无序容器的性能取决于hash函数的质量和桶的数目和大小。

    下面是管理桶的一些函数:

    操    作 描    述
    X(n, hf, eq)

    创建一个至少包含n个桶的空容器,并将hf用作哈希函数,将eq用作键值相等谓词。

    如果省略了eq,则将key_equal( )用作键值相等谓词;如果也省略了hf,则将hasher( )用作哈希函数

    X a(n, hf, eq)

    创建一个名为a的空容器,它至少包含n个桶,并将hf用作哈希函数,将eq用作键值相等谓词。

    如果省略eq,则将key_equal( )用作键值相等谓词;如果也省略了hf,则将hasher( )用作哈希函数

    X(i, j, n, hf, eq)

    创建一个至少包含n个桶的空容器,将hf用作哈希函数,将eq用作键值相等谓词,并插入区间[i, j]中的元素。

    如果省略了eq,将key_equal( )用作键值相等谓词;如果省略了hf,将hasher( )用作哈希函数;如果省略了n,则包含桶数不确定

    X a(i, j, n, hf, eq)

    创建一个名为a的的空容器,它至少包含n个桶,将hf用作哈希函数,将eq用作键值相等谓词,并插入区间[i, j]中的元素。

    如果省略了eq,将key_equal( )用作键值相等谓词;如果省略了hf,将hasher( )用作哈希函数;如果省略了n,则包含桶数不确定

    b.hash_function( ) 返回b使用的哈希函数
    b.key_eq( ) 返回创建b时使用的键值相等谓词
    桶接口
    b.bucket_count( ) 返回b包含的桶数
    b.max_bucket_count ( ) 返回一个上限数,它指定了b最多可包含多少个桶
    b.bucket(k) 返回键值为k的元素所属桶的索引
    b.bucket_size(n) 返回索引为n的桶可包含的元素数
    桶迭代
    b.begin(n) 返回一个迭代器,它指向索引为n的桶中的第一个元素
    b.end(n) 返回一个迭代器,它指向索引为n的桶中的最后一个元素
    b.cbegin(n) 返回一个常量迭代器,它指向索引为n的桶中的第一个元素
    b.cend(n) 返回一个常量迭代器,它指向索引为n的桶中的最后一个元素
    hash策略
    b.load_factor() 返回每个桶包含的平均元素数
    b.max_load_factor() 返回负载系数的最大可能取值;超过这个值后,容器将增加桶
    b.max_load_factor(z) 可能修改最大负载系统,建议将它设置为z
    a.rehash(n) 将桶数调整为不小于n,并确保a.bucket_count( )> a.size( ) / a.max_load_factor( )
    a.reserve(n) 等价于a.rehash(ceil(n/a.max_load_factor( ))),其中ceil(x)返回不小于x的最小整数

    默认情况下,无序容器使用关键字类型的==运算符来比较元素,它们还使用一个hash<key_type>类型对象来生成每个元素的hash值。(标准库为每个内置对象,包括指针提供了hash模板;并且,为一些标准库类型定义了hash,例如string、智能指针)

    那么如何使用自定义类型定义无序容器:

    size_t hasher(const Sales &sl){
        return hash<int>()(sl.id);//hash<int>是hash函数的函数对象size_t operator()(int _x)
    }
    
    bool equalId(const Sales &s1, const Sales &s2){
        return s1.id == s2.id;
    }
    unordered_multiset<Sales, decltype(hasher)*, decltype(equalId)*>bookstore(10, hasher, equalId);//参数是桶大小、哈希函数指针、相等性判断
  • 相关阅读:
    JavaScript实现类的private、protected、public、static以及继承
    OSS网页上传和断点续传(STSToken篇)
    OSS网页上传和断点续传(OSS配置篇)
    Linq sum()时遇到NULL
    SQLSERVER事务日志已满 the transaction log for database 'xx' is full
    笔记本高分辨软件兼容问题,字体太小或模糊
    H5上传图片之canvas
    An error occurred while updating the entries. See the inner exception for details.
    无限级结构SQL查询所有的下级和所有的上级
    SQLserver 进程被死锁问题解决
  • 原文地址:https://www.cnblogs.com/yeqluofwupheng/p/6865956.html
Copyright © 2011-2022 走看看